← Back to team overview

widelands-dev team mailing list archive

Re: [Merge] lp:~widelands-dev/widelands/new-shipping into lp:widelands

 

I have added some comments

Diff comments:

> 
> === modified file 'src/logic/map_objects/tribes/ship.cc'
> --- src/logic/map_objects/tribes/ship.cc	2019-05-27 14:28:34 +0000
> +++ src/logic/map_objects/tribes/ship.cc	2019-09-03 15:47:16 +0000
> @@ -736,22 +736,215 @@
>  	}
>  }
>  
> +bool Ship::has_destination(EditorGameBase& egbase, const PortDock& pd) const {
> +	for (const auto& pair : destinations_) {
> +		if (pair.first.get(egbase) == &pd) {
> +			return true;
> +		}
> +	}
> +	return false;
> +}
> +
>  /**
>   * Enter a new destination port for the ship.
>   * Call this after (un)loading the ship, for proper logging.
>   */
> -void Ship::set_destination(PortDock* pd) {
> -	destination_ = pd;
> -	if (pd) {
> -		molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd->serial(),
> -		      items_.size());
> -		pd->ship_coming(true);
> -	} else {
> -		molog("set_destination / none\n");
> +void Ship::push_destination(Game& game, PortDock& pd) {
> +	for (auto it = destinations_.begin(); it != destinations_.end(); ++it) {

for (const auto& pair : destinations_) {

> +		if (it->first.get(game) == &pd) {
> +			// We're already going there
> +			return;
> +		}
>  	}
> +	destinations_.push_back(std::make_pair(OPtr<PortDock>(&pd), 1));
> +	reorder_destinations(game);
> +	molog("push_destination(%u): rerouted to portdock %u (carrying %" PRIuS " items)\n",
> +			pd.serial(), get_current_destination(game)->serial(), items_.size());
> +	pd.ship_coming(*this, true);
>  	Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged));
>  }
>  
> +void Ship::clear_destinations(Game& game) {
> +	while (!destinations_.empty()) {
> +		pop_destination(game, *destinations_.front().first.get(game));
> +	}
> +}
> +
> +/**
> + * Remove this destination from the queue
> + */
> +void Ship::pop_destination(Game& game, PortDock& pd) {
> +	pd.ship_coming(*this, false);
> +	for (auto it = destinations_.begin(); it != destinations_.end(); ++it) {
> +		if (it->first.get(game) == &pd) {
> +			destinations_.erase(it);
> +			reorder_destinations(game);
> +			if (destinations_.empty()) {
> +				molog("pop_destination(%u): no destinations and %" PRIuS " items left\n",
> +						pd.serial(), items_.size());
> +			} else {
> +				molog("pop_destination(%u): rerouted to portdock %u (carrying %" PRIuS " items)\n",
> +						pd.serial(), get_current_destination(game)->serial(), items_.size());
> +			}
> +			return;
> +		}
> +	}
> +}
> +
> +// Recursively find the best ordering for our destinations
> +static inline float prioritised_distance(Path& path, uint32_t priority, uint32_t items) {
> +	return static_cast<float>(path.get_nsteps() * items) / (priority * priority);
> +}
> +using DestinationsQueue = std::vector<std::pair<PortDock*, uint32_t>>;
> +static std::pair<DestinationsQueue, float> shortest_order(Game& game,
> +                                                          Fleet& fleet,
> +                                                          bool is_on_dock,
> +                                                          void* start,
> +                                                          const DestinationsQueue& remaining_to_visit,
> +                                                          const std::map<PortDock*, uint32_t> shipping_items) {
> +	const size_t nr_dests = remaining_to_visit.size();
> +	assert(nr_dests > 0);
> +	if (nr_dests == 1) {
> +		// Recursion break: Only one portdock left
> +		Path path;
> +		if (is_on_dock) {
> +			PortDock* p = static_cast<PortDock*>(start);
> +			if (p != remaining_to_visit[0].first) {
> +				fleet.get_path(*p, *remaining_to_visit[0].first, path);
> +			}
> +		} else {
> +			static_cast<Ship*>(start)->calculate_sea_route(game, *remaining_to_visit[0].first, &path);
> +		}
> +		return std::pair<DestinationsQueue, float>(remaining_to_visit, prioritised_distance(
> +				path, remaining_to_visit[0].second, shipping_items.at(remaining_to_visit[0].first)));
> +	}
> +
> +	std::pair<DestinationsQueue, float> best_result;
> +	best_result.second = std::numeric_limits<float>::max();
> +	for (const auto& pair : remaining_to_visit) {
> +		DestinationsQueue remaining;
> +		for (const auto& p : remaining_to_visit) {
> +			if (p.first != pair.first) {
> +				remaining.push_back(p);
> +			}
> +		}
> +		auto result = shortest_order(game, fleet, true, pair.first, remaining, shipping_items);
> +		result.first.emplace(result.first.begin(), pair);
> +		Path path;
> +		if (is_on_dock) {

We have the exact same if... else above. Is this worth pulling out a (lambda) function?

> +			PortDock* p = static_cast<PortDock*>(start);
> +			if (p != remaining_to_visit[0].first) {
> +				fleet.get_path(*static_cast<PortDock*>(start), *pair.first, path);
> +			}
> +		} else {
> +			static_cast<Ship*>(start)->calculate_sea_route(game, *pair.first, &path);
> +		}
> +		const float length = result.second + prioritised_distance(path, pair.second, shipping_items.at(pair.first));
> +		if (length < best_result.second) {
> +			best_result.first = result.first;
> +			best_result.second = length;
> +		}
> +	}
> +	assert(best_result.second < std::numeric_limits<float>::max());
> +	assert(best_result.first.size() == nr_dests);
> +	return best_result;
> +}
> +
> +void Ship::reorder_destinations(Game& game) {
> +	upcast(PortDock, pd, get_position().field->get_immovable());
> +	size_t nr_dests = 0;
> +	DestinationsQueue old_dq;
> +	PortDock* fallback_dest = nullptr;
> +	std::map<PortDock*, uint32_t> shipping_items;
> +	for (const auto& pair : destinations_) {
> +		PortDock* p = pair.first.get(game);
> +		assert(p);
> +		uint32_t nr_items = 0;
> +		for (const auto& si : items_) {
> +			if (si.get_destination(game) == p) {
> +				++nr_items;
> +			}
> +		}
> +		if (nr_items == 0 && p->count_waiting() == 0 && !p->expedition_started() && (!pd || pd->count_waiting(p) == 0)) {
> +			fallback_dest = p;
> +			// We don't need to go there anymore
> +			p->ship_coming(*this, false);
> +			continue;
> +		}
> +		old_dq.push_back(std::make_pair(p, pair.second));
> +		++nr_dests;
> +		shipping_items[p] = nr_items;
> +	}
> +	if (nr_dests <= 1) {
> +		destinations_.clear();
> +		if (nr_dests > 0) {
> +			destinations_.push_back(old_dq[0]);
> +		} else if (fallback_dest) {
> +			destinations_.push_back(std::make_pair(OPtr<PortDock>(fallback_dest), 1));
> +			fallback_dest->ship_coming(*this, true);
> +		}
> +		return;
> +	}
> +
> +	const OPtr<PortDock> old_dest = destinations_.front().first;
> +	DestinationsQueue dq = shortest_order(game, *fleet_, pd,
> +			pd ? static_cast<void*>(pd) : static_cast<void*>(this), old_dq, shipping_items).first;
> +	assert(dq.size() == nr_dests);
> +
> +	std::vector<std::pair<OPtr<PortDock>, uint32_t>> old_destinations = destinations_;
> +	const size_t nr_all_old_dests = old_destinations.size();
> +	destinations_.clear();
> +	size_t index = 0;
> +	for (const auto& pair : dq) {
> +		OPtr<PortDock> optr(pair.first);
> +		uint32_t priority = pair.second;
> +		size_t old_index = 0;
> +		for (;; ++old_index) {

How about for (; old_destinations[old_index].first != optr; ++old_index) {

This should also be restricted to debug builds, because it's only there for the assert.

> +			assert(old_index < nr_all_old_dests);
> +			if (old_destinations[old_index].first == optr) {
> +				break;
> +			}
> +		}
> +		if (old_index < index) {
> +			priority += index - old_index;
> +		}
> +		destinations_.push_back(std::make_pair(optr, priority));
> +		++index;
> +	}
> +	if (old_dest != destinations_.front().first) {
> +		send_signal(game, "wakeup");
> +	}
> +}
> +
> +/**
> + * Returns an estimation for the time in arbitrary units from now until the moment when
> + * this ship will probably arrive at the given PortDock. This may change later when
> + * destinations are added or removed.
> + * Returns -1 if we are not planning to visit this PortDock or if we are not planning

-1 would be clearer throughout if it was a named constexpr - you use this flag in one of the functions above too.

> + * to visit the given intermediate portdock earlier than the destination.
> + */
> +int32_t Ship::estimated_arrival_time(Game& game, const PortDock& dest, const PortDock* intermediate) const {
> +	uint32_t time = 0;
> +	const PortDock* iterator = nullptr;
> +	for (const auto& pair : destinations_) {
> +		Path path;
> +		if (iterator) {

This if... else appears multiple times. Function time?

> +			fleet_->get_path(*iterator, *pair.first.get(game), path);
> +		} else {
> +			calculate_sea_route(game, *pair.first.get(game), &path);
> +		}
> +		iterator = pair.first.get(game);
> +		if (iterator == intermediate) {
> +			intermediate = nullptr;
> +		}
> +		time += path.get_nsteps();
> +		if (iterator == &dest) {
> +			return intermediate ? -1 : time;
> +		}
> +	}
> +	return -1;
> +}
> +
>  void Ship::add_item(Game& game, const ShippingItem& item) {
>  	assert(items_.size() < descr().get_capacity());
>  


-- 
https://code.launchpad.net/~widelands-dev/widelands/new-shipping/+merge/371155
Your team Widelands Developers is subscribed to branch lp:~widelands-dev/widelands/new-shipping.


References