← Back to team overview

widelands-dev team mailing list archive

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

 

ypopezios has proposed merging lp:~widelands-dev/widelands/congestion into lp:widelands.

Commit message:
Another effort to get windows builds.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1535115 in widelands: "ai roads getting jammed a lot"
  https://bugs.launchpad.net/widelands/+bug/1535115

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/congestion/+merge/348501
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/congestion into lp:widelands.
=== modified file 'src/economy/flag.cc'
--- src/economy/flag.cc	2018-04-07 16:59:00 +0000
+++ src/economy/flag.cc	2018-06-25 21:00:26 +0000
@@ -312,6 +312,22 @@
 }
 
 /**
+ * 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;
+	if (ware_filled_ >= ware_capacity_) return false;
+
+	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;
+	}
+	return true;
+}
+
+/**
  * Returns true if the flag can hold more wares.
 */
 bool Flag::has_capacity() const {
@@ -339,10 +355,13 @@
 }
 
 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
+}
 
-	PendingWare& pi = wares_[ware_filled_++];
+void Flag::init_ware(EditorGameBase& egbase, WareInstance& ware, PendingWare& pi) {
 	pi.ware = &ware;
 	pi.pending = false;
 	pi.nextstep = nullptr;
@@ -362,29 +381,22 @@
 	}
 
 	ware.set_location(egbase, this);
-
-	if (upcast(Game, game, &egbase))
-		ware.update(*game);  //  will call call_carrier() if necessary
 }
 
 /**
- * \return true if an ware is currently waiting for a carrier to the given Flag.
+ * \return a ware currently waiting for a carrier to the given destflag.
  *
  * \note Due to fetch_from_flag() semantics, this function makes no sense
  * for a  building destination.
 */
-bool Flag::has_pending_ware(Game&, Flag& dest) {
+Flag::PendingWare* Flag::has_pending_ware_for_flag(Flag& destflag) {
 	for (int32_t i = 0; i < ware_filled_; ++i) {
-		if (!wares_[i].pending)
-			continue;
-
-		if (wares_[i].nextstep != &dest)
-			continue;
-
-		return true;
+		PendingWare* pw = &wares_[i];
+		if (pw->pending && pw->nextstep == &destflag && 
+			destflag.allow_ware_from_flag(*pw->ware, *this)) return pw;
 	}
 
-	return false;
+	return nullptr;
 }
 
 /**
@@ -394,101 +406,112 @@
 #define MAX_TRANSFER_PRIORITY 16
 
 /**
- * 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::ack_pickup(Game&, Flag& destflag) {
-	int32_t highest_pri = -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 > 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 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 an ware was indeed
  * made pending again
  */
 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;
+		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 (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_[0].get(game);
-		capacity_wait_.erase(capacity_wait_.begin());
-		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;
-	}
-
+ * 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 or -1 if not found appropriate
+*/
+int32_t Flag::find_pending_ware(PlayerImmovable& dest) {
+	int32_t highest_pri = -1;
+	int32_t best_index = -1;
+	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 are currently carrying a ware.
+ * \return -2 if denied drop
+*/
+int32_t Flag::find_swapable_ware(WareInstance& ware, Flag& destflag) {
+	DescriptionIndex const descr_index = ware.descr_index();
+	int32_t highest_pri = -1;
+	int32_t best_index = -1;
+	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 > -1) {
+		return (ware_filled_ > ware_capacity_ - 3 || has_allowed) ? best_index : -1;
+	} else {
+		return (ware_filled_ < ware_capacity_ - 2 ||
+			(ware_filled_ < ware_capacity_ && !has_same_ware)) ? -1 : -2;
+	}
+}
+
+/**
+ * Called by carrier code to retrieve a ware found by the previous methods.
+*/
+WareInstance* Flag::fetch_pending_ware(Game& game, int32_t best_index) {
 	if (best_index < 0)
 		return nullptr;
 
@@ -499,14 +522,39 @@
 	        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_[0].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 <= 6; ++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->has_pending_ware_for_flag(*this);
+		if (pw && road->notify_ware(game, *other)) {
+			pw->pending = false;
+		}
+	}	
+}
+
+/**
  * Return a List of all the wares currently on this Flag. Do not rely
  * the result value to stay valid and do not change them
  */
@@ -532,7 +580,7 @@
 		memmove(&wares_[i], &wares_[i + 1], sizeof(wares_[0]) * (ware_filled_ - i));
 
 		if (upcast(Game, game, &egbase))
-			wake_up_capacity_queue(*game);
+			ware_departing(*game);
 
 		return;
 	}
@@ -600,27 +648,16 @@
 
 	for (int32_t dir = 1; dir <= 6; ++dir) {
 		Road* const road = get_road(dir);
-		Flag* other;
-		Road::FlagId flagid;
-
-		if (!road) {
-			continue;
-		}
-
-		if (&road->get_flag(Road::FlagStart) == this) {
-			flagid = Road::FlagStart;
-			other = &road->get_flag(Road::FlagEnd);
-		} else {
-			flagid = Road::FlagEnd;
+		if (!road) continue;
+
+		Flag* other = &road->get_flag(Road::FlagEnd);
+		if (other == this) {
 			other = &road->get_flag(Road::FlagStart);
 		}
-
-		if (other != &nextflag) {
-			continue;
-		}
+		if (other != &nextflag) continue;
 
 		// Yes, this is the road we want; inform it
-		if (road->notify_ware(game, flagid)) {
+		if (other->update_ware_from_flag(game, wares_[i], *road, *this)) {
 			return;
 		}
 
@@ -634,6 +671,70 @@
 }
 
 /**
+ * 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_swapable = false;
+	for (int i = 0; i < ware_filled_; ++i) {
+		PendingWare& pw = wares_[i];
+		if (pw.pending && pw.nextstep == &flag) {
+			has_swapable = true;
+		} else if (pw.ware->descr_index() == descr_index) {
+			return false;
+		}
+	}
+	return (ware_filled_ < ware_capacity_ || has_swapable) ? true : false;
+}
+
+/**
+ * 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_swapable = 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_swapable = true;
+		}
+	}
+
+	// ask road for carrier on low congestion-risk
+	if (ware_filled_ < ware_capacity_ - 2 || 
+		(!has_same_ware && (ware_filled_ < ware_capacity_ || has_swapable))) {
+		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-04-07 16:59:00 +0000
+++ src/economy/flag.h	2018-06-25 21:00:26 +0000
@@ -116,7 +116,15 @@
 
 	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_;
 	}
@@ -126,10 +134,14 @@
 	void wait_for_capacity(Game&, Worker&);
 	void skip_wait_for_capacity(Game&, Worker&);
 	void add_ware(EditorGameBase&, WareInstance&);
-	bool has_pending_ware(Game&, Flag& destflag);
-	bool ack_pickup(Game&, Flag& destflag);
+	void init_ware(EditorGameBase&, WareInstance&, PendingWare&);
+	PendingWare* has_pending_ware_for_flag(Flag&);
 	bool cancel_pickup(Game&, Flag& destflag);
-	WareInstance* fetch_pending_ware(Game&, PlayerImmovable& dest);
+	void ware_departing(Game&);
+	bool allow_ware_from_flag(WareInstance&, Flag&);
+	int32_t find_swapable_ware(WareInstance&, Flag&);
+	int32_t find_pending_ware(PlayerImmovable&);
+	WareInstance* fetch_pending_ware(Game&, int32_t);
 	Wares get_wares();
 
 	void call_carrier(Game&, WareInstance&, PlayerImmovable* nextstep);
@@ -151,20 +163,13 @@
 	          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:
-	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 update_ware_from_flag(Game&, PendingWare&, Road&, Flag&);
 
 	struct FlagJob {
 		Request* request;

=== modified file 'src/economy/road.cc'
--- src/economy/road.cc	2018-04-07 16:59:00 +0000
+++ src/economy/road.cc	2018-06-25 21:00:26 +0000
@@ -542,7 +542,8 @@
  * Called by Flag code: an ware should be picked up from the given flag.
  * \return true if a carrier has been sent on its way, false otherwise.
  */
-bool Road::notify_ware(Game& game, FlagId const flagid) {
+bool Road::notify_ware(Game& game, Flag& flag) {
+	FlagId flagid = &flag == flags_[Road::FlagEnd] ? Road::FlagEnd : Road::FlagStart;
 	uint32_t const gametime = game.get_gametime();
 	assert(busyness_last_update_ <= gametime);
 	uint32_t const tdelta = gametime - busyness_last_update_;

=== modified file 'src/economy/road.h'
--- src/economy/road.h	2018-04-07 16:59:00 +0000
+++ src/economy/road.h	2018-06-25 21:00:26 +0000
@@ -107,7 +107,7 @@
 	void presplit(Game&, Coords split);
 	void postsplit(Game&, Flag&);
 
-	bool notify_ware(Game& game, FlagId flagid);
+	bool notify_ware(Game& game, Flag& flag);
 
 	void remove_worker(Worker&) override;
 	void assign_carrier(Carrier&, uint8_t);

=== modified file 'src/logic/map_objects/tribes/carrier.cc'
--- src/logic/map_objects/tribes/carrier.cc	2018-04-15 06:13:09 +0000
+++ src/logic/map_objects/tribes/carrier.cc	2018-06-25 21:00:26 +0000
@@ -43,6 +43,7 @@
                                      static_cast<Bob::Ptr>(&Carrier::road_pop), true};
 
 /**
+ * Called by road code when the carrier has arrived successfully.
  * Work on the given road, assume the location is correct.
 */
 void Carrier::start_task_road(Game& game) {
@@ -50,7 +51,7 @@
 
 	top_state().ivar1 = 0;
 
-	promised_pickup_to_ = NOONE;
+	operation_ = INIT;
 }
 
 /**
@@ -77,14 +78,14 @@
 		return pop_task(game);
 	}
 
-	// Check for pending wares
-	if (promised_pickup_to_ == NOONE)
-		find_pending_ware(game);
+	if (operation_ == INIT) {
+		operation_ = find_source_flag(game);
+	}
 
-	if (promised_pickup_to_ != NOONE) {
+	if (operation_ > NOP) {
 		if (state.ivar1) {
 			state.ivar1 = 0;
-			return start_task_transport(game, promised_pickup_to_);
+			return start_task_transport(game, operation_);
 		} else {
 			// Short delay before we move to pick up
 			state.ivar1 = 1;
@@ -114,10 +115,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 (promised_pickup_to_ != NOONE && get_location(game)) {
+	if (operation_ > NOP && get_location(game)) {
 		Road& road = dynamic_cast<Road&>(*get_location(game));
-		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& flag = road.get_flag(static_cast<Road::FlagId>(operation_));
+		Flag& otherflag = road.get_flag(static_cast<Road::FlagId>(operation_ ^ 1));
 
 		flag.cancel_pickup(game, otherflag);
 	}
@@ -155,35 +156,85 @@
 		return pop_task(game);
 	}
 
-	if (state.ivar1 == -1)
+	int32_t const ivar1 = state.ivar1;
+	if (ivar1 == -1)
 		// If we're "in" the target building, special code applies
-		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);
-
+		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
 		// A sanity check is necessary, in case the building has been destroyed
-		PlayerImmovable* const next = ware.get_next_move_step(game);
-
-		if (next && next != &flag && &next->base_flag() == &flag)
-			enter_building(game, state);
-
-		// If the flag is overloaded we are allowed to drop wares as
-		// long as we can pick another up. Otherwise we have to wait.
-		else if ((flag.has_capacity() || !swap_or_wait(game, state)) &&
-		         !start_task_walktoflag(game, state.ivar1 ^ 1))
-			// Drop the ware, possible exchanging it with another one
-			drop_ware(game, state);
+
+		PlayerImmovable* next = ware->get_next_move_step(game);
+		if (next && next != &flag && &next->base_flag() == &flag) {
+			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_swapable_ware(*ware, otherflag) :
+										flag.find_pending_ware(otherflag);
+		if (operation_ == WAIT) {
+			if (otherware_idx < -1) {
+				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 < -1) {
+			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) {
+			// Drop our ware
+			flag.add_ware(game, *fetch_carried_ware(game));
+		}
+
+		// Pick up new load, if any
+		if (otherware) {
+			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.has_pending_ware_for_flag(flag);
+			if (pw) {
+				pw->pending = false;
+
+				operation_ = state.ivar1 = dest ^ 1;
+				set_animation(game, descr().get_animation("idle"));
+				schedule_act(game, 20);
+			} else {
+				operation_ = NOP;
+				pop_task(game);
+			}
+		}
 	}
 }
 
@@ -197,9 +248,10 @@
 void Carrier::deliver_to_building(Game& game, State& state) {
 	BaseImmovable* const pos = game.map()[get_position()].get_immovable();
 
-	if (dynamic_cast<Flag const*>(pos))
+	if (dynamic_cast<Flag const*>(pos)) {
+		operation_ = INIT;
 		return pop_task(game);  //  we are done
-	else if (upcast(Building, building, pos)) {
+	} else if (upcast(Building, building, pos)) {
 		// Drop all wares addressed to this building
 		while (WareInstance* const ware = get_carried_ware(game)) {
 			// If the building has disappeared and immediately been replaced
@@ -230,201 +282,51 @@
 }
 
 /**
- * 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)) {
-			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));
-
-	if (promised_pickup_to_ == (state.ivar1 ^ 1)) {
-		// If there's an ware we acked, we can drop ours even if the flag is
-		// flooded
-		other = flag.fetch_pending_ware(game, road.get_flag(static_cast<Road::FlagId>(state.ivar1)));
-
-		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) {
-		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 an 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).
+ * Called by road code to indicate that the given flag
+ * (0 = start, 1 = end) has a ware ready for transfer.
  * \return true if the carrier is going to fetch it.
  */
 bool Carrier::notify_ware(Game& game, int32_t const flag) {
 	State& state = top_state();
 
-	// 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 an ware on an overloaded flag iff it can pick up an 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)
+	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_ == NOP) {
+		operation_ = flag;
 		send_signal(game, "ware");
-	else if (state.task == &taskWaitforcapacity)
-		send_signal(game, "wakeup");
+		return true;
+	}
 
-	return true;
+	return false;
 }
 
 /**
- * Find a pending ware on one of the road's flags, ack it and set promised_pickup_to_
- * accordingly.
+ * Find a pending ware meant for our road,
+ * remove its pending status, and
+ * \return the flag it is on.
  */
-void Carrier::find_pending_ware(Game& game) {
+int32_t Carrier::find_source_flag(Game& game) {
+	assert(operation_ == INIT);
+
 	Road& road = dynamic_cast<Road&>(*get_location(game));
-	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 an 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");
+	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.has_pending_ware_for_flag(farflag)) {
+		pw->pending = false;
+		return near;
+	} else if (pw = farflag.has_pending_ware_for_flag(nearflag)) {
+		pw->pending = false;
+		return near ^ 1;
+	} else {
+		return NOP;
 	}
 }
 
@@ -497,7 +399,7 @@
 
 	Worker::log_general_info(egbase);
 
-	molog("promised_pickup_to = %i\n", promised_pickup_to_);
+	molog("operation_ = %i\n", operation_);
 }
 
 /*
@@ -521,7 +423,7 @@
 		uint8_t packet_version = fr.unsigned_8();
 		if (packet_version == kCurrentPacketVersion) {
 			Carrier& carrier = get<Carrier>();
-			carrier.promised_pickup_to_ = fr.signed_32();
+			carrier.operation_ = fr.signed_32();
 		} else {
 			throw UnhandledVersionError("Carrier", packet_version, kCurrentPacketVersion);
 		}
@@ -546,7 +448,7 @@
 	Worker::do_save(egbase, mos, fw);
 
 	fw.unsigned_8(kCurrentPacketVersion);
-	fw.signed_32(promised_pickup_to_);
+	fw.signed_32(operation_);
 }
 
 CarrierDescr::CarrierDescr(const std::string& init_descname,

=== modified file 'src/logic/map_objects/tribes/carrier.h'
--- src/logic/map_objects/tribes/carrier.h	2018-04-15 06:13:09 +0000
+++ src/logic/map_objects/tribes/carrier.h	2018-06-25 21:00:26 +0000
@@ -24,6 +24,7 @@
 #include "logic/map_objects/tribes/worker.h"
 
 namespace Widelands {
+class PendingWare;
 
 class CarrierDescr : public WorkerDescr {
 public:
@@ -49,7 +50,7 @@
 	MO_DESCR(CarrierDescr)
 
 	explicit Carrier(const CarrierDescr& carrier_descr)
-	   : Worker(carrier_descr), promised_pickup_to_(NOONE) {
+	   : Worker(carrier_descr), operation_(NOP) {
 	}
 	~Carrier() override {
 	}
@@ -66,7 +67,7 @@
 	static Task const taskRoad;
 
 private:
-	void find_pending_ware(Game&);
+	int32_t find_source_flag(Game&);
 	int32_t find_closest_flag(Game&);
 
 	// internal task stuff
@@ -77,17 +78,14 @@
 	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 NOONE = -1;
-	static const int32_t START_FLAG = 0;
-	static const int32_t END_FLAG = 1;
-	int32_t promised_pickup_to_;
+	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 NOP = -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_;
 
 	// saving and loading
 protected:

=== modified file 'src/logic/map_objects/tribes/worker.cc'
--- src/logic/map_objects/tribes/worker.cc	2018-05-05 17:10:37 +0000
+++ src/logic/map_objects/tribes/worker.cc	2018-06-25 21:00:26 +0000
@@ -1840,12 +1840,11 @@
 		if (upcast(Flag, flag, pos)) {
 			// Is this "our" flag?
 			if (flag->get_building() == location) {
-				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
-					}
+				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
 				}
 
 				// Don't try to enter building if it is a dismantle site
@@ -2078,15 +2077,18 @@
 	if (ware) {
 		// We're in the building, walk onto the flag
 		if (upcast(Building, building, location)) {
-			if (start_task_waitforcapacity(game, building->base_flag()))
-				return;
-
-			return start_task_leavebuilding(game, false);  //  exit throttle
+			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);
+			}
+			return;
 		}
 
 		// We're on the flag, drop the ware and pause a little
 		if (upcast(Flag, flag, location)) {
-			if (flag->has_capacity()) {
+			if (flag->has_capacity_for_ware(*ware)) {
 				flag->add_ware(game, *fetch_carried_ware(game));
 
 				set_animation(game, descr().get_animation("idle"));
@@ -2166,9 +2168,10 @@
 
 		// 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
-		if (WareInstance* const ware =
-		       dynamic_cast<Flag&>(*location).fetch_pending_ware(game, employer)) {
+		Flag& flag = dynamic_cast<Flag&>(*location);
+		if (WareInstance* const ware = flag.fetch_pending_ware(game, flag.find_pending_ware(employer))) {
 			set_carried_ware(game, ware);
+			flag.ware_departing(game);
 		}
 
 		set_animation(game, descr().get_animation("idle"));
@@ -2231,24 +2234,15 @@
    static_cast<Bob::Ptr>(&Worker::waitforcapacity_pop), true};
 
 /**
- * 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.
+ * Pushes a wait task and
+ * adds the worker to the flag's wait queue.
  */
-bool Worker::start_task_waitforcapacity(Game& game, Flag& flag) {
-	if (flag.has_capacity())
-		return false;
-
+void Worker::start_task_waitforcapacity(Game& game, Flag& flag) {
 	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-04-07 16:59:00 +0000
+++ src/logic/map_objects/tribes/worker.h	2018-06-25 21:00:26 +0000
@@ -163,7 +163,7 @@
 	void start_task_releaserecruit(Game&, Worker&);
 	void start_task_fetchfromflag(Game&);
 
-	bool start_task_waitforcapacity(Game&, Flag&);
+	void start_task_waitforcapacity(Game&, Flag&);
 	void start_task_leavebuilding(Game&, bool changelocation);
 	void start_task_fugitive(Game&);