← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/remove-anti-congestion-algorithm into lp:widelands

 

GunChleoc has proposed merging lp:~widelands-dev/widelands/remove-anti-congestion-algorithm into lp:widelands.

Commit message:
Remove the anti-congestion algorithm introduced in r8775 because it has too many bugs in it.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1797213 in widelands: "Sometimes ware(s) are lying at a flag and get not transported."
  https://bugs.launchpad.net/widelands/+bug/1797213

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/remove-anti-congestion-algorithm/+merge/359294
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/remove-anti-congestion-algorithm into lp:widelands.
=== modified file 'src/economy/flag.cc'
--- src/economy/flag.cc	2018-09-04 15:48:47 +0000
+++ src/economy/flag.cc	2018-11-23 16:57:35 +0000
@@ -337,30 +337,7 @@
 }
 
 /**
- * Called by workers wanting to drop a ware to their building's flag.
- * \return true/allow on low congestion-risk.
- */
-bool Flag::has_capacity_for_ware(WareInstance& ware) const {
-	// avoid iteration for the easy cases
-	if (ware_filled_ < ware_capacity_ - 2) {
-		return true;  // more than two free slots, allow
-	}
-	if (ware_filled_ >= ware_capacity_) {
-		return false;  // all slots filled, no room
-	}
-
-	DescriptionIndex const descr_index = ware.descr_index();
-	for (int i = 0; i < ware_filled_; ++i) {
-		if (wares_[i].ware->descr_index() == descr_index) {
-			return false;  // ware of this type already present, leave room for other types
-		}
-	}
-	return true;  // ware of this type not present, allow
-}
-
-/**
- * \return true/allow if the flag can hold more wares of any type.
- * has_capacity_for_ware() also checks ware's type to prevent congestion.
+ * Returns true if the flag can hold more wares.
  */
 bool Flag::has_capacity() const {
 	return (ware_filled_ < ware_capacity_);
@@ -386,24 +363,13 @@
 		capacity_wait_.erase(it);
 }
 
-/**
- * Adds given ware to this flag.
- * Please check has_capacity() or better has_capacity_for_ware() before.
- */
 void Flag::add_ware(EditorGameBase& egbase, WareInstance& ware) {
+
 	assert(ware_filled_ < ware_capacity_);
-	init_ware(egbase, ware, wares_[ware_filled_++]);
-	if (upcast(Game, game, &egbase)) {
-		ware.update(*game);  //  will call call_carrier() if necessary
-	}
-}
 
-/**
- * Properly assigns given ware instance to given pending ware.
- */
-void Flag::init_ware(EditorGameBase& egbase, WareInstance& ware, PendingWare& pi) {
+	PendingWare& pi = wares_[ware_filled_++];
 	pi.ware = &ware;
-	pi.pending = true;
+	pi.pending = false;
 	pi.nextstep = nullptr;
 	pi.priority = 0;
 
@@ -423,24 +389,32 @@
 	}
 
 	ware.set_location(egbase, this);
+
+	if (upcast(Game, game, &egbase)) {
+		ware.update(*game);  //  will call call_carrier() if necessary
+	}
 }
 
 /**
- * \return a ware currently waiting for a carrier to the given destflag.
+ * \return true if a ware is currently waiting for a carrier to the given Flag.
  *
  * \note Due to fetch_from_flag() semantics, this function makes no sense
  * for a  building destination.
 */
-Flag::PendingWare* Flag::get_ware_for_flag(Flag& destflag, bool const pending_only) {
+bool Flag::has_pending_ware(Game&, Flag& dest) {
 	for (int32_t i = 0; i < ware_filled_; ++i) {
-		PendingWare* pw = &wares_[i];
-		if ((!pending_only || pw->pending) && pw->nextstep == &destflag &&
-		    destflag.allow_ware_from_flag(*pw->ware, *this)) {
-			return pw;
-		}
+		if (!wares_[i].pending) {
+			continue;
+		}
+
+		if (wares_[i].nextstep != &dest) {
+			continue;
+		}
+
+		return true;
 	}
 
-	return nullptr;
+	return false;
 }
 
 /**
@@ -450,124 +424,109 @@
 #define MAX_TRANSFER_PRIORITY 16
 
 /**
- * Called by the carriercode when the carrier is called away from his job
- * but has acknowledged a ware before. This ware is then freed again
- * to be picked by another carrier. Returns true if a ware was indeed
- * made pending again.
+ * Called by carrier code to indicate that the carrier is moving to pick up an
+ * ware. Ware with highest transfer priority is chosen.
+ * \return true if an ware is actually waiting for the carrier.
  */
-bool Flag::cancel_pickup(Game& game, Flag& destflag) {
+bool Flag::ack_pickup(Game&, Flag& destflag) {
+	int32_t highest_pri = -1;
+	int32_t i_pri = -1;
+
 	for (int32_t i = 0; i < ware_filled_; ++i) {
-		PendingWare& pw = wares_[i];
-		if (!pw.pending && pw.nextstep == &destflag) {
-			pw.pending = true;
-			pw.ware->update(game);  //  will call call_carrier() if necessary
-			return true;
-		}
+		if (!wares_[i].pending) {
+			continue;
+		}
+
+		if (wares_[i].nextstep != &destflag) {
+			continue;
+		}
+
+		if (wares_[i].priority > highest_pri) {
+			highest_pri = wares_[i].priority;
+			i_pri = i;
+
+			// Increase ware priority, it matters only if the ware has to wait.
+			if (wares_[i].priority < MAX_TRANSFER_PRIORITY) {
+				wares_[i].priority++;
+			}
+		}
+	}
+
+	if (i_pri >= 0) {
+		wares_[i_pri].pending = false;
+		return true;
 	}
 
 	return false;
 }
-
 /**
  * Called by carrier code to find the best among the wares on this flag
  * that are meant for the provided dest.
  * \return index of found ware (carrier will take it)
  * or kNotFoundAppropriate (carrier will leave empty-handed)
  */
-int32_t Flag::find_pending_ware(PlayerImmovable& dest) {
-	int32_t highest_pri = -1;
-	int32_t best_index = kNotFoundAppropriate;
-	bool ware_pended = false;
-
-	for (int32_t i = 0; i < ware_filled_; ++i) {
-		PendingWare& pw = wares_[i];
-		if (pw.nextstep != &dest) {
-			continue;
-		}
-
-		if (pw.priority < MAX_TRANSFER_PRIORITY) {
-			pw.priority++;
-		}
-		// Release promised pickup, in case we find a preferable ware
-		if (!ware_pended && !pw.pending) {
-			ware_pended = pw.pending = true;
-		}
-
-		// If dest is flag, we exclude wares that can stress it
-		if (&dest != building_ && !dynamic_cast<Flag&>(dest).allow_ware_from_flag(*pw.ware, *this)) {
-			continue;
-		}
-
-		if (pw.priority > highest_pri) {
-			highest_pri = pw.priority;
-			best_index = i;
-		}
-	}
-
-	return best_index;
-}
-
-/**
- * Like find_pending_ware() above, but for carriers who have a ware to drop on this flag.
- * \return same as find_pending_ware() above, plus kDenyDrop (carrier will wait)
- */
-int32_t Flag::find_swappable_ware(WareInstance& ware, Flag& destflag) {
-	DescriptionIndex const descr_index = ware.descr_index();
-	int32_t highest_pri = -1;
-	int32_t best_index = kNotFoundAppropriate;
-	bool has_same_ware = false;
-	bool has_allowed = false;
-	bool ware_pended = false;
-
-	for (int32_t i = 0; i < ware_filled_; ++i) {
-		PendingWare& pw = wares_[i];
-		if (pw.nextstep != &destflag) {
-			if (pw.ware->descr_index() == descr_index) {
-				has_same_ware = true;
-			}
-			continue;
-		}
-
-		if (pw.priority < MAX_TRANSFER_PRIORITY) {
-			pw.priority++;
-		}
-		// Release promised pickup, in case we find a preferable ware
-		if (!ware_pended && !pw.pending) {
-			ware_pended = pw.pending = true;
-		}
-
-		// We prefer to retrieve wares that won't stress the destflag
-		if (destflag.allow_ware_from_flag(*pw.ware, *this)) {
-			if (!has_allowed) {
-				has_allowed = true;
-				highest_pri = -1;
-			}
-		} else {
-			if (has_allowed) {
-				continue;
-			}
-		}
-
-		if (pw.priority > highest_pri) {
-			highest_pri = pw.priority;
-			best_index = i;
-		}
-	}
-
-	if (best_index > kNotFoundAppropriate) {
-		return (ware_filled_ > ware_capacity_ - 3 || has_allowed) ? best_index : kNotFoundAppropriate;
-	} else {
-		return (ware_filled_ < ware_capacity_ - 2 ||
-		        (ware_filled_ < ware_capacity_ && !has_same_ware)) ?
-		          kNotFoundAppropriate :
-		          kDenyDrop;
-	}
-}
-
-/**
- * Called by carrier code to retrieve a ware found by the previous methods.
- */
-WareInstance* Flag::fetch_pending_ware(Game& game, int32_t best_index) {
+bool Flag::cancel_pickup(Game& game, Flag& destflag) {
+	int32_t lowest_prio = MAX_TRANSFER_PRIORITY + 1;
+	int32_t i_pri = -1;
+
+	for (int32_t i = 0; i < ware_filled_; ++i) {
+		if (wares_[i].pending) {
+			continue;
+		}
+
+		if (wares_[i].nextstep != &destflag) {
+			continue;
+		}
+
+		if (wares_[i].priority < lowest_prio) {
+			lowest_prio = wares_[i].priority;
+			i_pri = i;
+		}
+	}
+
+	if (i_pri >= 0) {
+		wares_[i_pri].pending = true;
+		wares_[i_pri].ware->update(game);  //  will call call_carrier() if necessary
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Wake one sleeper from the capacity queue.
+*/
+void Flag::wake_up_capacity_queue(Game& game) {
+	while (!capacity_wait_.empty()) {
+		Worker* const w = capacity_wait_.front().get(game);
+		capacity_wait_.pop_front();
+		if (w && w->wakeup_flag_capacity(game, *this)) {
+			break;
+		}
+	}
+}
+
+/**
+ * Called by carrier code to retrieve one of the wares on the flag that is meant
+ * for that carrier.
+ *
+ * This function may return 0 even if \ref ack_pickup() has already been
+ * called successfully.
+*/
+WareInstance* Flag::fetch_pending_ware(Game& game, PlayerImmovable& dest) {
+	int32_t best_index = -1;
+
+	for (int32_t i = 0; i < ware_filled_; ++i) {
+		if (wares_[i].nextstep != &dest) {
+			continue;
+		}
+
+		// We prefer to retrieve wares that have already been acked
+		if (best_index < 0 || !wares_[i].pending) {
+			best_index = i;
+		}
+	}
+
 	if (best_index < 0) {
 		return nullptr;
 	}
@@ -579,43 +538,14 @@
 	        sizeof(wares_[0]) * (ware_filled_ - best_index));
 
 	ware->set_location(game, nullptr);
+
+	// wake up capacity wait queue
+	wake_up_capacity_queue(game);
+
 	return ware;
 }
 
 /**
- * Called by carrier code to notify waiting carriers
- * which may be interested in the new state of this flag.
- */
-void Flag::ware_departing(Game& game) {
-	// Wake up one sleeper from the capacity queue.
-	while (!capacity_wait_.empty()) {
-		Worker* const w = capacity_wait_.front().get(game);
-		capacity_wait_.erase(capacity_wait_.begin());
-		if (w && w->wakeup_flag_capacity(game, *this)) {
-			return;
-		}
-	}
-
-	// Consider pending wares of neighboring flags.
-	for (int32_t dir = 1; dir <= WalkingDir::LAST_DIRECTION; ++dir) {
-		Road* const road = get_road(dir);
-		if (!road) {
-			continue;
-		}
-
-		Flag* other = &road->get_flag(Road::FlagEnd);
-		if (other == this) {
-			other = &road->get_flag(Road::FlagStart);
-		}
-
-		PendingWare* pw = other->get_ware_for_flag(*this, kPendingOnly);
-		if (pw && road->notify_ware(game, *other)) {
-			pw->pending = false;
-		}
-	}
-}
-
-/**
  * Accelerate potential promotion of roads adjacent to a newly promoted road.
  */
 void Flag::propagate_promoted_road(Road* const promoted_road) {
@@ -684,7 +614,7 @@
 		memmove(&wares_[i], &wares_[i + 1], sizeof(wares_[0]) * (ware_filled_ - i));
 
 		if (upcast(Game, game, &egbase)) {
-			ware_departing(*game);
+			wake_up_capacity_queue(*game);
 		}
 
 		return;
@@ -755,20 +685,27 @@
 
 	for (int32_t dir = 1; dir <= 6; ++dir) {
 		Road* const road = get_road(dir);
+		Flag* other;
+		Road::FlagId flagid;
+
 		if (!road) {
 			continue;
 		}
 
-		Flag* other = &road->get_flag(Road::FlagEnd);
-		if (other == this) {
+		if (&road->get_flag(Road::FlagStart) == this) {
+			flagid = Road::FlagStart;
+			other = &road->get_flag(Road::FlagEnd);
+		} else {
+			flagid = Road::FlagEnd;
 			other = &road->get_flag(Road::FlagStart);
 		}
+
 		if (other != &nextflag) {
 			continue;
 		}
 
 		// Yes, this is the road we want; inform it
-		if (other->update_ware_from_flag(game, wares_[i], *road, *this)) {
+		if (road->notify_ware(game, flagid)) {
 			return;
 		}
 
@@ -782,72 +719,6 @@
 }
 
 /**
- * Called by neighboring flags, before agreeing for a carrier
- * to take one of their wares heading to this flag.
- * \return true/allow on low congestion-risk.
- */
-bool Flag::allow_ware_from_flag(WareInstance& ware, Flag& flag) {
-	// avoid iteration for the easy cases
-	if (ware_filled_ < ware_capacity_ - 2) {
-		return true;
-	}
-
-	DescriptionIndex const descr_index = ware.descr_index();
-	bool has_swappable = false;
-	for (int i = 0; i < ware_filled_; ++i) {
-		PendingWare& pw = wares_[i];
-		if (pw.pending && pw.nextstep == &flag) {
-			has_swappable = true;
-		} else if (pw.ware->descr_index() == descr_index) {
-			return false;
-		}
-	}
-	return ware_filled_ < ware_capacity_ || has_swappable;
-}
-
-/**
- * Called when a ware is trying to reach this flag through the provided road,
- * having just arrived to the provided flag.
- * Swaps pending wares if possible. Otherwise,
- * asks road for carrier on low congestion-risk.
- * \return false if the ware is not immediately served.
- */
-bool Flag::update_ware_from_flag(Game& game, PendingWare& pw1, Road& road, Flag& flag) {
-	WareInstance& w1 = *pw1.ware;
-	DescriptionIndex const w1_descr_index = w1.descr_index();
-	bool has_same_ware = false;
-	bool has_swappable = false;
-	for (int i = 0; i < ware_filled_; ++i) {
-		PendingWare& pw2 = wares_[i];
-		WareInstance& w2 = *pw2.ware;
-		if (w2.descr_index() == w1_descr_index) {
-			if (pw2.nextstep == &flag) {
-				// swap pending wares remotely
-				init_ware(game, w1, pw2);
-				flag.init_ware(game, w2, pw1);
-				w1.update(game);
-				w2.update(game);
-				return true;
-			}
-
-			has_same_ware = true;
-		} else if (pw2.pending && pw2.nextstep == &flag) {
-			has_swappable = true;
-		}
-	}
-
-	// ask road for carrier on low congestion-risk
-	if (ware_filled_ < ware_capacity_ - 2 ||
-	    (!has_same_ware && (ware_filled_ < ware_capacity_ || has_swappable))) {
-		if (road.notify_ware(game, flag)) {
-			pw1.pending = false;
-			return true;
-		}
-	}
-	return false;
-}
-
-/**
  * Called whenever a road gets broken or split.
  * Make sure all wares on this flag are rerouted if necessary.
  *

=== modified file 'src/economy/flag.h'
--- src/economy/flag.h	2018-09-25 06:32:35 +0000
+++ src/economy/flag.h	2018-11-23 16:57:35 +0000
@@ -47,10 +47,6 @@
 	DISALLOW_COPY_AND_ASSIGN(FlagDescr);
 };
 
-constexpr bool kPendingOnly = true;           // ignore non-pending wares
-constexpr int32_t kNotFoundAppropriate = -1;  // no ware appropiate for carrying
-constexpr int32_t kDenyDrop = -2;             // flag is full and no ware appropiate for swapping
-
 /**
  * Flag represents a flag as you see it on the map.
  *
@@ -125,15 +121,7 @@
 
 	bool is_dead_end() const;
 
-	struct PendingWare {
-		WareInstance* ware;              ///< the ware itself
-		bool pending;                    ///< if the ware is pending
-		int32_t priority;                ///< carrier prefers the ware with highest priority
-		OPtr<PlayerImmovable> nextstep;  ///< next step that this ware is sent to
-	};
-
 	bool has_capacity() const;
-	bool has_capacity_for_ware(WareInstance&) const;
 	uint32_t total_capacity() {
 		return ware_capacity_;
 	}
@@ -143,14 +131,10 @@
 	void wait_for_capacity(Game&, Worker&);
 	void skip_wait_for_capacity(Game&, Worker&);
 	void add_ware(EditorGameBase&, WareInstance&);
-	void init_ware(EditorGameBase&, WareInstance&, PendingWare&);
-	PendingWare* get_ware_for_flag(Flag&, bool pending_only = false);
+	bool has_pending_ware(Game&, Flag& destflag);
+	bool ack_pickup(Game&, Flag& destflag);
 	bool cancel_pickup(Game&, Flag& destflag);
-	void ware_departing(Game&);
-	bool allow_ware_from_flag(WareInstance&, Flag&);
-	int32_t find_swappable_ware(WareInstance&, Flag&);
-	int32_t find_pending_ware(PlayerImmovable&);
-	WareInstance* fetch_pending_ware(Game&, int32_t);
+	WareInstance* fetch_pending_ware(Game&, PlayerImmovable& dest);
 	void propagate_promoted_road(Road* promoted_road);
 	Wares get_wares();
 	uint8_t count_wares_in_queue(PlayerImmovable& dest) const;
@@ -171,13 +155,20 @@
 	void
 	draw(uint32_t gametime, const Vector2f& point_on_dst, float scale, RenderTarget* dst) override;
 
+	void wake_up_capacity_queue(Game&);
+
 	static void
 	flag_job_request_callback(Game&, Request&, DescriptionIndex, Worker*, PlayerImmovable&);
 
 	void set_flag_position(Coords coords);
 
 private:
-	bool update_ware_from_flag(Game&, PendingWare&, Road&, Flag&);
+	struct PendingWare {
+		WareInstance* ware;              ///< the ware itself
+		bool pending;                    ///< if the ware is pending
+		int32_t priority;                ///< carrier prefers the ware with highest priority
+		OPtr<PlayerImmovable> nextstep;  ///< next step that this ware is sent to
+	};
 
 	struct FlagJob {
 		Request* request;

=== modified file 'src/economy/road.cc'
--- src/economy/road.cc	2018-09-04 15:48:47 +0000
+++ src/economy/road.cc	2018-11-23 16:57:35 +0000
@@ -542,8 +542,7 @@
  * Try to pick up a ware from the given flag.
  * \return true if a carrier has been sent on its way, false otherwise.
  */
-bool Road::notify_ware(Game& game, Flag& flag) {
-	FlagId flagid = &flag == flags_[Road::FlagEnd] ? Road::FlagEnd : Road::FlagStart;
+bool Road::notify_ware(Game& game, FlagId const flagid) {
 	// Iterate over all carriers and try to find one which will take the ware
 	for (CarrierSlot& slot : carrier_slots_) {
 		if (Carrier* const carrier = slot.carrier.get(game)) {

=== modified file 'src/economy/road.h'
--- src/economy/road.h	2018-09-25 06:32:35 +0000
+++ src/economy/road.h	2018-11-23 16:57:35 +0000
@@ -113,7 +113,7 @@
 	void presplit(Game&, Coords split);
 	void postsplit(Game&, Flag&);
 
-	bool notify_ware(Game& game, Flag& flag);
+	bool notify_ware(Game& game, FlagId flagid);
 	void update_wallet_chargetime(Game& game);
 	void charge_wallet(Game& game);
 	int32_t wallet() const;
@@ -148,8 +148,8 @@
 	uint8_t carriers_count() const;
 
 private:
-	/// Counter that is incremented for every ware served by this road
-	/// according to the delay of service and decremented over time.
+	/// Counter that is incremented when a ware does not get a carrier for this
+	/// road immediately and decremented over time.
 	int32_t wallet_;
 
 	/// holds the gametime when wallet_ was last charged

=== modified file 'src/logic/map_objects/tribes/carrier.cc'
--- src/logic/map_objects/tribes/carrier.cc	2018-10-30 18:40:57 +0000
+++ src/logic/map_objects/tribes/carrier.cc	2018-11-23 16:57:35 +0000
@@ -51,7 +51,7 @@
 
 	top_state().ivar1 = 0;
 
-	operation_ = INIT;
+	promised_pickup_to_ = NOONE;
 }
 
 /**
@@ -78,14 +78,15 @@
 		return pop_task(game);
 	}
 
-	if (operation_ == INIT) {
-		operation_ = find_source_flag(game);
+	// Check for pending wares
+	if (promised_pickup_to_ == NOONE) {
+		find_pending_ware(game);
 	}
 
-	if (operation_ > NO_OPERATION) {
+	if (promised_pickup_to_ != NOONE) {
 		if (state.ivar1) {
 			state.ivar1 = 0;
-			return start_task_transport(game, operation_);
+			return start_task_transport(game, promised_pickup_to_);
 		} else {
 			// Short delay before we move to pick up
 			state.ivar1 = 1;
@@ -119,10 +120,10 @@
  * a ware there, we have to make sure that they do not count on us anymore.
  */
 void Carrier::road_pop(Game& game, State& /* state */) {
-	if (operation_ > NO_OPERATION && get_location(game)) {
+	if (promised_pickup_to_ != NOONE && get_location(game)) {
 		Road& road = dynamic_cast<Road&>(*get_location(game));
-		Flag& flag = road.get_flag(static_cast<Road::FlagId>(operation_));
-		Flag& otherflag = road.get_flag(static_cast<Road::FlagId>(operation_ ^ 1));
+		Flag& flag = road.get_flag(static_cast<Road::FlagId>(promised_pickup_to_));
+		Flag& otherflag = road.get_flag(static_cast<Road::FlagId>(promised_pickup_to_ ^ 1));
 
 		flag.cancel_pickup(game, otherflag);
 	}
@@ -160,99 +161,35 @@
 		return pop_task(game);
 	}
 
-	int32_t const ivar1 = state.ivar1;
-	if (ivar1 == -1) {
+	if (state.ivar1 == -1) {
 		// If we're "in" the target building, special code applies
-		return deliver_to_building(game, state);
-	}
-
-	WareInstance* ware = get_carried_ware(game);
-	if (ware) {
-		assert(ware->get_location(game) == this);
-	}
-
-	Road& road = dynamic_cast<Road&>(*get_location(game));
-	int32_t const dest = ware ? ivar1 ^ 1 : ivar1;
-	Flag& flag = road.get_flag(static_cast<Road::FlagId>(dest));
-
-	if (ware) {
-		// If the ware should go to the building attached to our flag,
-		// walk directly into said building
+		deliver_to_building(game, state);
+	} else if (!does_carry_ware()) {
+		// If we don't carry something, walk to the flag
+		pickup_from_flag(game, state);
+	} else {
+		Road& road = dynamic_cast<Road&>(*get_location(game));
+		// If the ware should go to the building attached to our flag, walk
+		// directly into said building
+		Flag& flag = road.get_flag(static_cast<Road::FlagId>(state.ivar1 ^ 1));
+
+		WareInstance& ware = *get_carried_ware(game);
+		assert(ware.get_location(game) == this);
+
 		// A sanity check is necessary, in case the building has been destroyed
+		PlayerImmovable* const next = ware.get_next_move_step(game);
 
-		PlayerImmovable* const next = ware->get_next_move_step(game);
 		if (next && next != &flag && &next->base_flag() == &flag) {
 			// pay some coins before entering the building,
 			// to compensate for the time to be spent in its street-segment
 			road.pay_for_building();
-
-			if (!start_task_walktoflag(game, dest)) {
-				// Enter building
-				state.ivar1 = -1;
-				start_task_move(game, WALK_NW, descr().get_right_walk_anims(does_carry_ware()), true);
-			}
-			return;
-		}
-	}
-
-	if (!start_task_walktoflag(game, dest, operation_ == WAIT)) {
-		// If the flag is overloaded we are allowed to drop wares,
-		// as long as we can pick another up. Otherwise we have to wait.
-
-		Flag& otherflag = road.get_flag(static_cast<Road::FlagId>(dest ^ 1));
-		int32_t otherware_idx =
-		   ware ? flag.find_swappable_ware(*ware, otherflag) : flag.find_pending_ware(otherflag);
-		if (operation_ == WAIT) {
-			if (otherware_idx < kNotFoundAppropriate) {
-				return start_task_waitforcapacity(game, flag);  // join flag's wait queue
-			} else {
-				operation_ = dest ^ 1;  // resume transport without joining flag's wait queue
-				set_animation(game, descr().get_animation("idle"));
-				return schedule_act(game, 20);
-			}
-		} else if (otherware_idx < kNotFoundAppropriate) {
-			operation_ = WAIT;  // move one node away
-			set_animation(game, descr().get_animation("idle"));
-			return schedule_act(game, 20);
-		}
-
-		WareInstance* otherware = flag.fetch_pending_ware(game, otherware_idx);
-
-		if (ware) {
-			const bool ware_astray = (ware->get_next_move_step(game) == nullptr);
-			// Drop our ware
-			flag.add_ware(game, *fetch_carried_ware(game));
-			// If the destination of the dropped ware changed while carrying it and we don't have
-			// anything else we should carry, we might pick it up again immediately, so check again
-			if (ware_astray && otherware_idx == kNotFoundAppropriate) {
-				otherware_idx = flag.find_pending_ware(otherflag);
-				otherware = flag.fetch_pending_ware(game, otherware_idx);
-			}
-		}
-
-		// Pick up new load, if any
-		if (otherware) {
-			// pay before getting the ware, while checking for road promotion
-			road.pay_for_road(game, flag.count_wares_in_queue(otherflag));
-
-			set_carried_ware(game, otherware);
-			flag.ware_departing(game);
-
-			operation_ = state.ivar1 = dest;
-			set_animation(game, descr().get_animation("idle"));
-			schedule_act(game, 20);
-		} else {
-			Flag::PendingWare* pw = otherflag.get_ware_for_flag(flag, kPendingOnly);
-			if (pw) {
-				pw->pending = false;
-
-				operation_ = state.ivar1 = dest ^ 1;
-				set_animation(game, descr().get_animation("idle"));
-				schedule_act(game, 20);
-			} else {
-				operation_ = NO_OPERATION;
-				pop_task(game);
-			}
+			enter_building(game, state);
+		} else if ((flag.has_capacity() || !swap_or_wait(game, state)) &&
+				   !start_task_walktoflag(game, state.ivar1 ^ 1)) {
+			// If the flag is overloaded we are allowed to drop wares as
+			// long as we can pick another up. Otherwise we have to wait.
+			// Drop the ware, possible exchanging it with another one
+			drop_ware(game, state);
 		}
 	}
 }
@@ -268,7 +205,6 @@
 	BaseImmovable* const pos = game.map()[get_position()].get_immovable();
 
 	if (dynamic_cast<Flag const*>(pos)) {
-		operation_ = INIT;
 		return pop_task(game);  //  we are done
 	} else if (upcast(Building, building, pos)) {
 		// Drop all wares addressed to this building
@@ -301,51 +237,211 @@
 }
 
 /**
- * Called by road code to indicate that the given flag
- * (0 = start, 1 = end) has a ware ready for transfer.
+ * Walks to the queued flag and picks up one acked ware
+ *
+ * \param g Game the carrier lives on
+ * \param s Flags sent to the task
+ */
+void Carrier::pickup_from_flag(Game& game, State& state) {
+	int32_t const ivar1 = state.ivar1;
+	if (!start_task_walktoflag(game, ivar1)) {
+
+		promised_pickup_to_ = NOONE;
+
+		Road& road = dynamic_cast<Road&>(*get_location(game));
+		Flag& flag = road.get_flag(static_cast<Road::FlagId>(ivar1));
+		Flag& otherflag = road.get_flag(static_cast<Road::FlagId>(ivar1 ^ 1));
+
+		// Are there wares to move between our flags?
+		if (WareInstance* const ware = flag.fetch_pending_ware(game, otherflag)) {
+			// pay before getting the ware, while checking for road promotion
+			road.pay_for_road(game, flag.count_wares_in_queue(otherflag));
+			set_carried_ware(game, ware);
+
+			set_animation(game, descr().get_animation("idle"));
+			return schedule_act(game, 20);
+		} else {
+			molog("[Carrier]: Nothing suitable on flag.\n");
+			return pop_task(game);
+		}
+	}
+}
+
+/**
+ * Drop one ware in a flag, and pick up a new one if we acked it
+ *
+ * \param g Game the carrier lives on.
+ * \param s Flags sent to the task
+ */
+void Carrier::drop_ware(Game& game, State& state) {
+	WareInstance* other = nullptr;
+	Road& road = dynamic_cast<Road&>(*get_location(game));
+	Flag& flag = road.get_flag(static_cast<Road::FlagId>(state.ivar1 ^ 1));
+	Flag& otherflag = road.get_flag(static_cast<Road::FlagId>(state.ivar1));
+
+	if (promised_pickup_to_ == (state.ivar1 ^ 1)) {
+		// If there's a ware we acked, we can drop ours even if the flag is
+		// flooded
+		other = flag.fetch_pending_ware(game, otherflag);
+
+		if (!other && !flag.has_capacity()) {
+			molog("[Carrier]: strange: acked ware from busy flag no longer "
+			      "present.\n");
+
+			promised_pickup_to_ = NOONE;
+			set_animation(game, descr().get_animation("idle"));
+			return schedule_act(game, 20);
+		}
+
+		state.ivar1 = promised_pickup_to_;
+		promised_pickup_to_ = NOONE;
+	}
+
+	// Drop our ware
+	flag.add_ware(game, *fetch_carried_ware(game));
+
+	// Pick up new load, if any
+	if (other) {
+		// pay before getting the ware, while checking for road promotion
+		road.pay_for_road(game, flag.count_wares_in_queue(otherflag));
+		set_carried_ware(game, other);
+
+		set_animation(game, descr().get_animation("idle"));
+		return schedule_act(game, 20);
+	} else {
+		return pop_task(game);
+	}
+}
+
+/**
+ * When picking up wares, if some of them is targeted to the building attached
+ * to target flag walk straight into it and deliver.
+ *
+ * \param g Game the carrier lives on.
+ * \param s Flags sent to the task.
+ */
+void Carrier::enter_building(Game& game, State& state) {
+	if (!start_task_walktoflag(game, state.ivar1 ^ 1)) {
+		state.ivar1 = -1;
+		return start_task_move(game, WALK_NW, descr().get_right_walk_anims(does_carry_ware()), true);
+	}
+}
+
+/**
+ * Swaps wares from an overloaded flag for as long as the carrier can pick
+ * up new wares from it. Otherwise, changes the carrier state to wait.
+ *
+ * \param g Game the carrier lives on.
+ * \param s Flags sent to the task.
+ *
+ * \return true if the carrier must wait before delivering his wares.
+ */
+bool Carrier::swap_or_wait(Game& game, State& state) {
+	// Road that employs us
+	Road& road = dynamic_cast<Road&>(*get_location(game));
+	// Flag we are delivering to
+	Flag& flag = road.get_flag(static_cast<Road::FlagId>(state.ivar1 ^ 1));
+	// The other flag of our road
+	Flag& otherflag = road.get_flag(static_cast<Road::FlagId>(state.ivar1));
+
+	if (promised_pickup_to_ == (state.ivar1 ^ 1)) {
+		// All is well, we already acked a ware that we can pick up
+		// from this flag
+		return false;
+	} else if (flag.has_pending_ware(game, otherflag)) {
+		if (!flag.ack_pickup(game, otherflag)) {
+			throw wexception(
+			   "MO(%u): transport: overload exchange: flag %u is fucked up", serial(), flag.serial());
+		}
+
+		promised_pickup_to_ = state.ivar1 ^ 1;
+		return false;
+	} else if (!start_task_walktoflag(game, state.ivar1 ^ 1, true)) {
+		start_task_waitforcapacity(game, flag);  //  wait one node away
+	}
+
+	return true;
+}
+
+/**
+ * Called by Road code to indicate that a new ware has arrived on a flag
+ * (0 = start, 1 = end).
  * \return true if the carrier is going to fetch it.
  */
 bool Carrier::notify_ware(Game& game, int32_t const flag) {
 	State& state = top_state();
 
-	if (operation_ == WAIT) {
-		if (state.objvar1.get(game) ==
-		    &dynamic_cast<Road&>(*get_location(game)).get_flag(static_cast<Road::FlagId>(flag))) {
-			operation_ = flag;
-			send_signal(game, "wakeup");
-			return true;
-		}
-	} else if (operation_ == NO_OPERATION) {
-		operation_ = flag;
+	// Check if we've already acked something
+	if (promised_pickup_to_ != NOONE)
+		return false;
+
+	// If we are currently in a transport.
+	// Explanation:
+	//  a) a different carrier / road may be better suited for this ware
+	//     (the transport code does not have priorities for the actual
+	//     carrier that is notified)
+	//  b) the transport task has logic that allows it to
+	//     drop a ware on an overloaded flag iff it can pick up a ware
+	//     at the same time.
+	//     We should ack said ware to avoid more confusion before we move
+	//     onto the flag, but we can't do that if we have already acked
+	//     something.
+	//  c) we might ack for a flag that we are actually moving away from;
+	//     this will get us into trouble if wares have arrived on the other
+	//     flag while we couldn't ack them.
+	//
+	// (Maybe the need for this lengthy explanation is proof that the
+	// ack system needs to be reworked.)
+	if (State const* const transport = get_state(taskTransport))
+		if ((transport->ivar1 == -1 && find_closest_flag(game) != flag) || flag == transport->ivar1)
+			return false;
+
+	// Ack it if we haven't
+	promised_pickup_to_ = flag;
+
+	if (state.task == &taskRoad) {
 		send_signal(game, "ware");
-		return true;
+	} else if (state.task == &taskWaitforcapacity) {
+		send_signal(game, "wakeup");
 	}
-
-	return false;
+	return true;
 }
 
 /**
- * Find a pending ware meant for our road,
- * remove its pending status, and
- * \return the flag it is on.
+ * Find a pending ware on one of the road's flags, ack it and set promised_pickup_to_
+ * accordingly.
  */
-int32_t Carrier::find_source_flag(Game& game) {
-	assert(operation_ == INIT);
-
+void Carrier::find_pending_ware(Game& game) {
 	Road& road = dynamic_cast<Road&>(*get_location(game));
-	int32_t near = find_closest_flag(game);
-	Flag& nearflag = road.get_flag(static_cast<Road::FlagId>(near));
-	Flag& farflag = road.get_flag(static_cast<Road::FlagId>(near ^ 1));
-
-	Flag::PendingWare* pw;
-	if ((pw = nearflag.get_ware_for_flag(farflag))) {
-		pw->pending = false;
-		return near;
-	} else if ((pw = farflag.get_ware_for_flag(nearflag, kPendingOnly))) {
-		pw->pending = false;
-		return near ^ 1;
-	} else {
-		return NO_OPERATION;
+	uint32_t havewarebits = 0;
+
+	assert(promised_pickup_to_ == NOONE);
+
+	if (road.get_flag(Road::FlagStart).has_pending_ware(game, road.get_flag(Road::FlagEnd))) {
+		havewarebits |= 1;
+	}
+
+	if (road.get_flag(Road::FlagEnd).has_pending_ware(game, road.get_flag(Road::FlagStart))) {
+		havewarebits |= 2;
+	}
+
+	//  If both flags have a ware, we pick the one closer to us.
+	if (havewarebits == 3) {
+		havewarebits = 1 << find_closest_flag(game);
+	}
+
+	// Ack our decision
+	if (havewarebits == 1) {
+		promised_pickup_to_ = START_FLAG;
+		if (!road.get_flag(Road::FlagStart).ack_pickup(game, road.get_flag(Road::FlagEnd))) {
+			throw wexception("Carrier::find_pending_ware: start flag is messed up");
+		}
+
+	} else if (havewarebits == 2) {
+		promised_pickup_to_ = END_FLAG;
+		if (!road.get_flag(Road::FlagEnd).ack_pickup(game, road.get_flag(Road::FlagStart))) {
+			throw wexception("Carrier::find_pending_ware: end flag is messed up");
+		}
 	}
 }
 
@@ -418,7 +514,7 @@
 
 	Worker::log_general_info(egbase);
 
-	molog("operation = %i\n", operation_);
+	molog("promised_pickup_to = %i\n", promised_pickup_to_);
 }
 
 /*
@@ -428,7 +524,7 @@
 
 ==============================
 */
-constexpr uint8_t kCurrentPacketVersion = 2;
+constexpr uint8_t kCurrentPacketVersion = 3;
 
 Carrier::Loader::Loader() {
 }
@@ -437,10 +533,11 @@
 	Worker::Loader::load(fr);
 
 	try {
-		uint8_t packet_version = fr.unsigned_8();
-		if (packet_version == kCurrentPacketVersion) {
+		const uint8_t packet_version = fr.unsigned_8();
+		// TODO(GunChleoc): Remove savegame compatibility after Build 21.
+		if (packet_version <= kCurrentPacketVersion && packet_version >= 1) {
 			Carrier& carrier = get<Carrier>();
-			carrier.operation_ = fr.signed_32();
+			carrier.promised_pickup_to_ = fr.signed_32();
 		} else {
 			throw UnhandledVersionError("Carrier", packet_version, kCurrentPacketVersion);
 		}
@@ -465,7 +562,7 @@
 	Worker::do_save(egbase, mos, fw);
 
 	fw.unsigned_8(kCurrentPacketVersion);
-	fw.signed_32(operation_);
+	fw.signed_32(promised_pickup_to_);
 }
 
 CarrierDescr::CarrierDescr(const std::string& init_descname,

=== modified file 'src/logic/map_objects/tribes/carrier.h'
--- src/logic/map_objects/tribes/carrier.h	2018-09-04 15:48:47 +0000
+++ src/logic/map_objects/tribes/carrier.h	2018-11-23 16:57:35 +0000
@@ -24,7 +24,6 @@
 #include "logic/map_objects/tribes/worker.h"
 
 namespace Widelands {
-class PendingWare;
 
 class CarrierDescr : public WorkerDescr {
 public:
@@ -50,7 +49,7 @@
 	MO_DESCR(CarrierDescr)
 
 	explicit Carrier(const CarrierDescr& carrier_descr)
-	   : Worker(carrier_descr), operation_(NO_OPERATION) {
+	   : Worker(carrier_descr), promised_pickup_to_(NOONE) {
 	}
 	~Carrier() override {
 	}
@@ -67,7 +66,7 @@
 	static Task const taskRoad;
 
 private:
-	int32_t find_source_flag(Game&);
+	void find_pending_ware(Game&);
 	int32_t find_closest_flag(Game&);
 
 	// internal task stuff
@@ -78,14 +77,17 @@
 	static Task const taskTransport;
 
 	void deliver_to_building(Game&, State&);
+	void pickup_from_flag(Game&, State&);
+	void drop_ware(Game&, State&);
+	void enter_building(Game&, State&);
+	bool swap_or_wait(Game&, State&);
 
+	/// -1: no ware acked; 0/1: acked ware for start/end flag of road
 	// This should be an enum, but this clutters the code with too many casts
-	static const int32_t INIT = -3;          // ready to undertake or resume operations
-	static const int32_t WAIT = -2;          // waiting for flag capacity
-	static const int32_t NO_OPERATION = -1;  // idling
-	static const int32_t START_FLAG = 0;     // serving start flag of road
-	static const int32_t END_FLAG = 1;       // serving end flag of road
-	int32_t operation_;
+	static const int32_t NOONE = -1;
+	static const int32_t START_FLAG = 0;
+	static const int32_t END_FLAG = 1;
+	int32_t promised_pickup_to_;
 
 	// saving and loading
 protected:

=== modified file 'src/logic/map_objects/tribes/worker.cc'
--- src/logic/map_objects/tribes/worker.cc	2018-11-07 10:19:29 +0000
+++ src/logic/map_objects/tribes/worker.cc	2018-11-23 16:57:35 +0000
@@ -137,11 +137,10 @@
 		totalres += amount;
 		totalchance += 8 * amount;
 
-		//  Add penalty for fields that are running out
-		//  Except for totally depleted fields or wrong ressource fields
-		//  if we already know there is no ressource (left) we won't mine there
+		// Add penalty for fields that are running out
 		if (amount == 0)
-			totalchance += 0;
+			// we already know it's completely empty, so punish is less
+			totalchance += 1;
 		else if (amount <= 2)
 			totalchance += 6;
 		else if (amount <= 4)
@@ -1819,11 +1818,12 @@
 		if (upcast(Flag, flag, pos)) {
 			// Is this "our" flag?
 			if (flag->get_building() == location) {
-				WareInstance* const ware = get_carried_ware(game);
-				if (state.ivar1 && ware && flag->has_capacity_for_ware(*ware)) {
-					flag->add_ware(game, *fetch_carried_ware(game));
-					set_animation(game, descr().get_animation("idle"));
-					return schedule_act(game, 20);  //  rest a while
+				if (state.ivar1 && flag->has_capacity()) {
+					if (WareInstance* const ware = fetch_carried_ware(game)) {
+						flag->add_ware(game, *ware);
+						set_animation(game, descr().get_animation("idle"));
+						return schedule_act(game, 20);  //  rest a while
+					}
 				}
 
 				// Don't try to enter building if it is a dismantle site
@@ -2056,18 +2056,16 @@
 	if (ware) {
 		// We're in the building, walk onto the flag
 		if (upcast(Building, building, location)) {
-			Flag& baseflag = building->base_flag();
-			if (baseflag.has_capacity_for_ware(*ware)) {
-				start_task_leavebuilding(game, false);  //  exit throttle
-			} else {
-				start_task_waitforcapacity(game, baseflag);
+			if (start_task_waitforcapacity(game, building->base_flag())) {
+				return;
 			}
-			return;
+
+			return start_task_leavebuilding(game, false);  //  exit throttle
 		}
 
 		// We're on the flag, drop the ware and pause a little
 		if (upcast(Flag, flag, location)) {
-			if (flag->has_capacity_for_ware(*ware)) {
+			if (flag->has_capacity()) {
 				flag->add_ware(game, *fetch_carried_ware(game));
 
 				set_animation(game, descr().get_animation("idle"));
@@ -2147,11 +2145,9 @@
 
 		// The ware has decided that it doesn't want to go to us after all
 		// In order to return to the warehouse, we're switching to State_DropOff
-		Flag& flag = dynamic_cast<Flag&>(*location);
 		if (WareInstance* const ware =
-		       flag.fetch_pending_ware(game, flag.find_pending_ware(employer))) {
+		       dynamic_cast<Flag&>(*location).fetch_pending_ware(game, employer)) {
 			set_carried_ware(game, ware);
-			flag.ware_departing(game);
 		}
 
 		set_animation(game, descr().get_animation("idle"));
@@ -2214,15 +2210,24 @@
    static_cast<Bob::Ptr>(&Worker::waitforcapacity_pop), true};
 
 /**
- * Pushes a wait task and
- * adds the worker to the flag's wait queue.
+ * Checks the capacity of the flag.
+ *
+ * If there is none, a wait task is pushed, and the worker is added to the
+ * flag's wait queue. The function returns true in this case.
+ * If the flag still has capacity, the function returns false and doesn't
+ * act at all.
  */
-void Worker::start_task_waitforcapacity(Game& game, Flag& flag) {
+bool Worker::start_task_waitforcapacity(Game& game, Flag& flag) {
+	if (flag.has_capacity())
+		return false;
+
 	push_task(game, taskWaitforcapacity);
 
 	top_state().objvar1 = &flag;
 
 	flag.wait_for_capacity(game, *this);
+
+	return true;
 }
 
 void Worker::waitforcapacity_update(Game& game, State&) {

=== modified file 'src/logic/map_objects/tribes/worker.h'
--- src/logic/map_objects/tribes/worker.h	2018-09-14 08:46:36 +0000
+++ src/logic/map_objects/tribes/worker.h	2018-11-23 16:57:35 +0000
@@ -163,7 +163,7 @@
 	void start_task_releaserecruit(Game&, Worker&);
 	void start_task_fetchfromflag(Game&);
 
-	void start_task_waitforcapacity(Game&, Flag&);
+	bool start_task_waitforcapacity(Game&, Flag&);
 	void start_task_leavebuilding(Game&, bool changelocation);
 	void start_task_fugitive(Game&);
 


Follow ups