Merge lp:~widelands-dev/widelands/ship_scheduling_2 into lp:widelands

Proposed by ypopezios
Status: Superseded
Proposed branch: lp:~widelands-dev/widelands/ship_scheduling_2
Merge into: lp:widelands
Diff against target: 1132 lines (+417/-335)
8 files modified
src/economy/fleet.cc (+195/-219)
src/economy/fleet.h (+6/-1)
src/economy/portdock.cc (+125/-71)
src/economy/portdock.h (+9/-7)
src/economy/shippingitem.cc (+1/-1)
src/economy/shippingitem.h (+1/-1)
src/logic/map_objects/tribes/ship.cc (+74/-32)
src/logic/map_objects/tribes/ship.h (+6/-3)
To merge this branch: bzr merge lp:~widelands-dev/widelands/ship_scheduling_2
Reviewer Review Type Date Requested Status
GunChleoc Needs Fixing
Review via email: mp+354019@code.launchpad.net

This proposal supersedes a proposal from 2018-08-04.

This proposal has been superseded by a proposal from 2018-09-21.

Commit message

Improved ware/worker routing algorithm for ships and portdocks.

Description of the change

To post a comment you must log in.
Revision history for this message
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal

Continuous integration builds have changed state:

Travis build 3754. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/412020649.
Appveyor build 3554. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_ship_scheduling_2-3554.

Revision history for this message
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal

Continuous integration builds have changed state:

Travis build 3758. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/412187264.
Appveyor build 3558. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_ship_scheduling_2-3558.

Revision history for this message
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal

Continuous integration builds have changed state:

Travis build 3855. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/422224908.
Appveyor build 3653. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_ship_scheduling_2-3653.

Revision history for this message
ypopezios (ypopezios) wrote :

AppVeyor seems broken. Otherwise, this branch is now mature and big enough. Not going to add anything more into it, so as to keep it easier for reviewers. If people test it, it can make it into build 20.

Revision history for this message
hessenfarmer (stephan-lutz) wrote :

Reason for appveyor not working anymore currently is that the glbinding package has been upgraded in the repo of MSYS from 2.1.4 to 3.0.2.1 which seems to have issues with our code now.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I have pushed a commit with a code review and some small tweaks. Please have a look at the NOCOM comments.

Not tested yet.

Revision history for this message
ypopezios (ypopezios) wrote :

Added inline comments concerning the NOCOM comments.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 3961. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/428035209.
Appveyor build 3759. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_ship_scheduling_2-3759.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I had actually programmed a really nice endless loop with those iterators... all fixed now. I have also improved the savegame compatibility, so it's only calculated once during game loading.

Thanks for this great feature! :)

@bunnybot merge

review: Approve
Revision history for this message
GunChleoc (gunchleoc) wrote :

One of the tests in our test suite has an endless loop in it:

test/maps/ship_transportation.wmf/scripting/test_rip_ports_with_worker_in_transit.lua

This already happened with older versions of this branch, so I don't think that it's anything I did.

review: Needs Fixing
Revision history for this message
bunnybot (widelandsofficial) wrote :

Refusing to merge, since Travis is not green. Use @bunnybot merge force for merging anyways.

Travis build 3977. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/428723984.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/economy/fleet.cc'
2--- src/economy/fleet.cc 2018-09-05 06:42:21 +0000
3+++ src/economy/fleet.cc 2018-09-21 19:37:16 +0000
4@@ -300,7 +300,7 @@
5 *
6 * @return true if successful, or false if the docks are not actually part of the fleet.
7 */
8-bool Fleet::get_path(PortDock& start, PortDock& end, Path& path) {
9+bool Fleet::get_path(const PortDock& start, const PortDock& end, Path& path) {
10 uint32_t startidx = std::find(ports_.begin(), ports_.end(), &start) - ports_.begin();
11 uint32_t endidx = std::find(ports_.begin(), ports_.end(), &end) - ports_.begin();
12
13@@ -628,8 +628,7 @@
14 }
15
16 /**
17- * Act callback updates ship scheduling. All decisions about where transport ships
18- * are supposed to go are made via this function.
19+ * Act callback updates ship scheduling of idle ships.
20 *
21 * @note Do not call this directly; instead, trigger it via @ref update
22 */
23@@ -637,230 +636,207 @@
24 act_pending_ = false;
25
26 if (!active()) {
27- // If we are here, most likely act() was called by a port with waiting wares or an expedition
28- // ready
29- // although there are still no ships. We can't handle it now, so we reschedule the act()
30- schedule_act(game, 5000); // retry in the next time
31+ // If we are here, most likely act() was called by a port with waiting wares or
32+ // with an expedition ready, although there are still no ships.
33+ // We can't handle it now, so we reschedule the act()
34+ schedule_act(game, kFleetInterval); // retry in the next time
35 act_pending_ = true;
36 return;
37 }
38
39 molog("Fleet::act\n");
40
41- // we need to calculate what ship is to be send to which port
42- // for this we will have temporary data structure with format
43- // <<ship,port>,score>
44- // where ship and port are not objects but positions in ports_ and ships_
45- // this is to allow native hashing
46- std::map<std::pair<uint16_t, uint16_t>, uint16_t> scores;
47-
48- // so we will identify all pairs: idle ship : ports, and score all such
49- // pairs. We consider
50- // - count of wares onboard, first ware (oldest) is counted as 8 (prioritization)
51- // (counting wares for particular port only)
52- // - count wares waiting at the port/3
53- // - distance between ship and a port (0-10 points, the closer the more points)
54- // - is another ship heading there right now?
55-
56- // at the end we must know if requrests of all ports asking for ship were addressed
57- // if any unsatisfied, we must schedule new run of this function
58- // when we send a ship there, the port is removed from list
59- std::list<uint16_t> waiting_ports;
60-
61- // this is just helper - first member of scores map
62- std::pair<uint16_t, uint16_t> mapping; // ship number, port number
63-
64- // first we go over ships - idle ones (=without destination)
65- // then over wares on these ships and create first ship-port
66- // pairs with score
67- for (uint16_t s = 0; s < ships_.size(); s += 1) {
68- if (ships_[s]->get_destination(game)) {
69- continue;
70- }
71- if (ships_[s]->get_ship_state() != Ship::ShipStates::kTransport) {
72- continue; // in expedition obviously
73- }
74-
75- for (uint16_t i = 0; i < ships_[s]->get_nritems(); i += 1) {
76- PortDock* dst = ships_[s]->items_[i].get_destination(game);
77- if (!dst) {
78- // if wares without destination on ship without destination
79- // such ship can be send to any port, and should be sent
80- // to some port, so we add 1 point to score for each port
81- for (uint16_t p = 0; p < ports_.size(); p += 1) {
82- mapping.first = s;
83- mapping.second = p;
84- scores[mapping] += 1;
85- }
86- continue;
87- }
88-
89- bool destination_found = false; // Just a functional check
90- for (uint16_t p = 0; p < ports_.size(); p += 1) {
91- if (ports_[p] == ships_[s]->items_[i].get_destination(game)) {
92- mapping.first = s;
93- mapping.second = p;
94- scores[mapping] += (i == 0) ? 8 : 1;
95- destination_found = true;
96- }
97- }
98- if (!destination_found) {
99- // Perhaps the throw here is too strong
100- // we can still remove it before stable release if it proves too much
101- // during my testing this situation never happened
102- throw wexception("A ware with destination that does not match any of player's"
103- " ports, ship %u, ware's destination: %u",
104- ships_[s]->serial(),
105- ships_[s]->items_[i].get_destination(game)->serial());
106- }
107- }
108- }
109-
110- // now opposite aproach - we go over ports to find out those that have wares
111- // waiting for ship then find candidate ships to satisfy the requests
112- for (uint16_t p = 0; p < ports_.size(); p += 1) {
113- PortDock& pd = *ports_[p];
114- if (!pd.get_need_ship()) {
115- continue;
116- }
117-
118- // general stategy is "one ship for port is enough", but sometimes
119- // amount of ware waiting for ship is too high
120- if (count_ships_heading_here(game, &pd) * 25 > pd.count_waiting()) {
121- continue;
122- }
123-
124- waiting_ports.push_back(p);
125-
126- // scoring and entering the pair into scores (or increasing existing
127- // score if the pair is already there)
128- for (uint16_t s = 0; s < ships_.size(); s += 1) {
129-
130- if (ships_[s]->get_destination(game)) {
131- continue; // already has destination
132- }
133-
134- if (ships_[s]->get_ship_state() != Ship::ShipStates::kTransport) {
135- continue; // in expedition obviously
136- }
137-
138- mapping.first = s;
139- mapping.second = p;
140- // following aproximately considers free capacity of a ship
141- scores[mapping] += ((ships_[s]->get_nritems() > 15) ? 1 : 3) +
142- std::min(ships_[s]->descr().get_capacity() - ships_[s]->get_nritems(),
143- ports_[p]->count_waiting()) /
144- 3;
145- }
146- }
147-
148- // Now adding score for distance
149- for (auto ship_port_relation : scores) {
150-
151- // here we get distance ship->port
152- // possibilities are:
153- // - we are in port and it is the same as target port
154- // - we are in other port, then we use get_dock() function to fetch precalculated path
155- // - if above fails, we calculate path "manually"
156- int16_t route_length = -1;
157-
158- PortDock* current_portdock =
159- get_dock(game, ships_[ship_port_relation.first.first]->get_position());
160-
161- if (current_portdock) { // we try to use precalculated paths of game
162-
163- // we are in the same portdock
164- if (current_portdock == ports_[ship_port_relation.first.second]) {
165- route_length = 0;
166- } else { // it is different portdock then
167- Path tmp_path;
168- if (get_path(*current_portdock, *ports_[ship_port_relation.first.second], tmp_path)) {
169- route_length = tmp_path.get_nsteps();
170- }
171- }
172- }
173-
174- // most probably the ship is not in a portdock (should not happen frequently)
175- if (route_length == -1) {
176- route_length = ships_[ship_port_relation.first.first]->calculate_sea_route(
177- game, *ports_[ship_port_relation.first.second]);
178- }
179-
180- // now we have length of route, so we need to calculate score
181- int16_t score_for_distance = 0;
182- if (route_length < 3) {
183- score_for_distance = 10;
184- } else {
185- score_for_distance = 8 - route_length / 50;
186- }
187- // must not be negative
188- score_for_distance = (score_for_distance < 0) ? 0 : score_for_distance;
189-
190- scores[ship_port_relation.first] += score_for_distance;
191- }
192-
193- // looking for best scores and sending ships accordingly
194- uint16_t best_ship = 0;
195- uint16_t best_port = 0;
196-
197- // after sending a ship we will remove one or more items from scores
198- while (!scores.empty()) {
199- uint16_t best_score = 0;
200-
201- // searching for combination with highest score
202- for (const auto& combination : scores) {
203- if (combination.second > best_score) {
204- best_score = combination.second;
205- best_ship = combination.first.first;
206- best_port = combination.first.second;
207- }
208- }
209- if (best_score == 0) {
210- // this is check of correctnes of this algorithm, this should not happen
211- throw wexception("Fleet::act(): No port-destination pair selected or its score is zero");
212- }
213-
214- // making sure the winner has no destination set
215- assert(!ships_[best_ship]->get_destination(game));
216-
217- // now actual setting destination for "best ship"
218- ships_[best_ship]->set_destination(game, *ports_[best_port]);
219- molog("... ship %u sent to port %u, wares onboard: %2d, the port is asking for a ship: %s\n",
220- ships_[best_ship]->serial(), ports_[best_port]->serial(),
221- ships_[best_ship]->get_nritems(), (ports_[best_port]->get_need_ship()) ? "yes" : "no");
222-
223- // pruning the scores table
224- // the ship that was just sent somewhere cannot be send elsewhere :)
225- for (auto it = scores.cbegin(); it != scores.cend();) {
226-
227- // decreasing score for target port as there was a ship just sent there
228- if (it->first.second == best_port) {
229- mapping.first = it->first.first;
230- mapping.second = it->first.second;
231- scores[mapping] /= 2;
232- // just make sure it is nonzero
233- scores[mapping] = (scores[mapping] == 0) ? 1 : scores[mapping];
234- }
235-
236- // but removing all pairs where best ship is participating as it is not available anymore
237- // (because it was sent to "best port")
238- if (it->first.first == best_ship) {
239- scores.erase(it++);
240- } else {
241- ++it;
242- }
243- }
244-
245- // also removing the port from waiting_ports
246- waiting_ports.remove(best_port);
247- }
248-
249- if (!waiting_ports.empty()) {
250- molog("... there are %" PRIuS " ports requesting ship(s) we cannot satisfy yet\n",
251- waiting_ports.size());
252- schedule_act(game, 5000); // retry next time
253+ // For each waiting port, try to find idle ships and send to it the closest one.
254+ uint16_t waiting_ports = ports_.size();
255+ for (PortDock* p : ports_) {
256+ if (p->get_need_ship() == 0) {
257+ --waiting_ports;
258+ continue;
259+ }
260+
261+ Ship* closest_ship = nullptr;
262+ uint32_t shortest_dist = kRouteNotCalculated;
263+ bool waiting = true;
264+
265+ for (Ship* s : ships_) {
266+ if (s->get_destination(game)) {
267+ if (s->get_destination(game) == p) {
268+ waiting = false;
269+ --waiting_ports;
270+ break;
271+ }
272+ continue; // The ship already has a destination
273+ }
274+ if (s->get_ship_state() != Ship::ShipStates::kTransport) {
275+ continue; // Ship is not available, e.g. in expedition
276+ }
277+
278+ // Here we get distance ship->port
279+ uint32_t route_length = kRouteNotCalculated;
280+
281+ // Get precalculated distance for ships available at ports
282+ {
283+ PortDock* cur_port = get_dock(game, s->get_position());
284+ if (cur_port) { // Ship is at a port
285+ if (cur_port == p) { // Same port
286+ route_length = 0;
287+ } else { // Different port
288+ Path precalculated_path;
289+ if (get_path(*cur_port, *p, precalculated_path)) {
290+ route_length = precalculated_path.get_nsteps();
291+ }
292+ }
293+ }
294+ }
295+
296+ // Get distance for ships available but not at a port (should not happen frequently)
297+ if (route_length == kRouteNotCalculated) {
298+ route_length = s->calculate_sea_route(game, *p);
299+ }
300+
301+ if (route_length < shortest_dist) {
302+ shortest_dist = route_length;
303+ closest_ship = s;
304+ }
305+ }
306+
307+ if (waiting && closest_ship) {
308+ --waiting_ports;
309+ closest_ship->set_destination(p);
310+ closest_ship->send_signal(game, "wakeup");
311+ }
312+ }
313+
314+ if (waiting_ports > 0) {
315+ molog("... there are %u ports requesting ship(s) we cannot satisfy yet\n", waiting_ports);
316+ schedule_act(game, kFleetInterval); // retry next time
317 act_pending_ = true;
318 }
319+
320+ // Deal with edge-case of losing destination before reaching it
321+ for (Ship* s : ships_) {
322+ if (s->get_destination(game)) {
323+ continue; // The ship has a destination
324+ }
325+ if (s->get_ship_state() != Ship::ShipStates::kTransport) {
326+ continue; // Ship is not available, e.g. in expedition
327+ }
328+ if (s->items_.empty()) {
329+ continue; // No pending wares/workers
330+ }
331+
332+ // Send ship to the closest port
333+ PortDock* closest_port = nullptr;
334+ uint32_t shortest_dist = kRouteNotCalculated;
335+
336+ for (PortDock* p : ports_) {
337+ uint32_t route_length = s->calculate_sea_route(game, *p);
338+ if (route_length < shortest_dist) {
339+ shortest_dist = route_length;
340+ closest_port = p;
341+ }
342+ }
343+
344+ if (closest_port) {
345+ s->set_destination(closest_port);
346+ s->send_signal(game, "wakeup");
347+ }
348+ }
349+}
350+
351+/**
352+ * For the given three consecutive ports, decide if their path is favourable or not.
353+ * \return true if the path from start to finish >= the path from middle to finish
354+ */
355+bool Fleet::is_path_favourable(const PortDock& start, const PortDock& middle, const PortDock& finish) {
356+ if (&middle != &finish) {
357+ Path path_start_to_finish;
358+ Path path_middle_to_finish;
359+#ifndef NDEBUG
360+ assert(get_path(start, finish, path_start_to_finish));
361+#else
362+ get_path(start, finish, path_start_to_finish);
363+#endif
364+ if (get_path(middle, finish, path_middle_to_finish)) {
365+ if (path_middle_to_finish.get_nsteps() > path_start_to_finish.get_nsteps()) {
366+ return false;
367+ }
368+ }
369+ }
370+ return true; // default
371+}
372+
373+/**
374+ * For the given ship, go through all ports of this fleet
375+ * and find the one with the best score.
376+ * \return that port
377+ */
378+PortDock* Fleet::find_next_dest(Game& game, const Ship& ship, const PortDock& from_port) {
379+ PortDock* best_port = nullptr;
380+ float best_score = 0.0f;
381+
382+ for (PortDock* p : ports_) {
383+ if (p == &from_port) {
384+ continue; // same port
385+ }
386+
387+ float score = 0.0f;
388+ WareInstance* ware;
389+ Worker* worker;
390+
391+ // Score for wares/workers onboard that ship for that port
392+ for (const ShippingItem& si : ship.items_) {
393+ if (si.get_destination(game) == p) {
394+ si.get(game, &ware, &worker);
395+ if (ware) {
396+ score += 1; // TODO(ypopezios): increase by ware's importance
397+ } else { // worker
398+ score += 4;
399+ }
400+ }
401+ }
402+
403+ // Score for wares/workers waiting at that port
404+ for (const ShippingItem& si : from_port.waiting_) {
405+ if (si.get_destination(game) == p) {
406+ si.get(game, &ware, &worker);
407+ if (ware) {
408+ score += 1; // TODO(ypopezios): increase by ware's importance
409+ } else { // worker
410+ score += 4;
411+ }
412+ }
413+ }
414+
415+ if (score == 0.0f && p->get_need_ship() == 0) {
416+ continue; // empty ship to empty port
417+ }
418+
419+ // Here we get distance ship->port
420+ uint32_t route_length = kRouteNotCalculated;
421+
422+ // Get precalculated distance if the ship is at a port
423+ {
424+ Path precalculated_path;
425+ if (get_path(from_port, *p, precalculated_path)) { // try to use precalculated path
426+ route_length = precalculated_path.get_nsteps();
427+ }
428+ }
429+
430+ // Get distance for when the ship is not at a port (should not happen frequently)
431+ if (route_length == kRouteNotCalculated) {
432+ route_length = ship.calculate_sea_route(game, *p);
433+ }
434+
435+ score = (score + 1.0f) * (score + p->get_need_ship());
436+ score = score * (1.0f - route_length / (score + route_length));
437+ if (score > best_score) {
438+ best_score = score;
439+ best_port = p;
440+ }
441+ }
442+
443+ return best_port;
444 }
445
446 void Fleet::log_general_info(const EditorGameBase& egbase) const {
447
448=== modified file 'src/economy/fleet.h'
449--- src/economy/fleet.h 2018-09-04 15:48:47 +0000
450+++ src/economy/fleet.h 2018-09-21 19:37:16 +0000
451@@ -46,6 +46,9 @@
452 DISALLOW_COPY_AND_ASSIGN(FleetDescr);
453 };
454
455+constexpr int32_t kFleetInterval = 5000;
456+constexpr uint32_t kRouteNotCalculated = std::numeric_limits<uint32_t>::max();
457+
458 /**
459 * Manage all ships and ports of a player that are connected
460 * by ocean.
461@@ -96,7 +99,7 @@
462
463 void log_general_info(const EditorGameBase&) const override;
464
465- bool get_path(PortDock& start, PortDock& end, Path& path);
466+ bool get_path(const PortDock& start, const PortDock& end, Path& path);
467 void add_neighbours(PortDock& pd, std::vector<RoutingNodeNeighbour>& neighbours);
468
469 uint32_t count_ships() const;
470@@ -152,6 +155,8 @@
471 void save(EditorGameBase&, MapObjectSaver&, FileWrite&) override;
472
473 static MapObject::Loader* load(EditorGameBase&, MapObjectLoader&, FileRead&);
474+ bool is_path_favourable(const PortDock& start, const PortDock& middle, const PortDock& finish);
475+ PortDock* find_next_dest(Game&, const Ship&, const PortDock& from_port);
476 };
477
478 } // namespace Widelands
479
480=== modified file 'src/economy/portdock.cc'
481--- src/economy/portdock.cc 2018-09-04 15:48:47 +0000
482+++ src/economy/portdock.cc 2018-09-21 19:37:16 +0000
483@@ -34,6 +34,7 @@
484 #include "logic/game_data_error.h"
485 #include "logic/map_objects/tribes/ship.h"
486 #include "logic/map_objects/tribes/warehouse.h"
487+#include "logic/path.h"
488 #include "logic/player.h"
489 #include "logic/widelands_geometry_io.h"
490 #include "map_io/map_object_loader.h"
491@@ -55,7 +56,7 @@
492 : PlayerImmovable(g_portdock_descr),
493 fleet_(nullptr),
494 warehouse_(wh),
495- need_ship_(false),
496+ ships_coming_(0),
497 expedition_ready_(false) {
498 }
499
500@@ -116,6 +117,10 @@
501 return nullptr;
502 }
503
504+uint32_t PortDock::get_need_ship() const {
505+ return (waiting_.size() + (expedition_ready_ ? 20 : 0)) / (ships_coming_ + 1);
506+}
507+
508 /**
509 * Signal to the dock that it now belongs to the given economy.
510 *
511@@ -247,7 +252,7 @@
512 * its route.
513 */
514 void PortDock::update_shippingitem(Game& game, WareInstance& ware) {
515- for (std::vector<ShippingItem>::iterator item_iter = waiting_.begin();
516+ for (auto item_iter = waiting_.begin();
517 item_iter != waiting_.end(); ++item_iter) {
518
519 if (item_iter->object_.serial() == ware.serial()) {
520@@ -271,7 +276,7 @@
521 * updated its route.
522 */
523 void PortDock::update_shippingitem(Game& game, Worker& worker) {
524- for (std::vector<ShippingItem>::iterator item_iter = waiting_.begin();
525+ for (auto item_iter = waiting_.begin();
526 item_iter != waiting_.end(); ++item_iter) {
527
528 if (item_iter->object_.serial() == worker.serial()) {
529@@ -281,44 +286,66 @@
530 }
531 }
532
533-void PortDock::update_shippingitem(Game& game, std::vector<ShippingItem>::iterator it) {
534+void PortDock::update_shippingitem(Game& game, std::list<ShippingItem>::iterator it) {
535 it->update_destination(game, *this);
536
537- PortDock* dst = it->get_destination(game);
538+ const PortDock* dst = it->get_destination(game);
539 assert(dst != this);
540
541 // Destination might have vanished or be in another economy altogether.
542 if (dst && dst->get_economy() == get_economy()) {
543- set_need_ship(game, true);
544+ if (ships_coming_ <= 0) {
545+ set_need_ship(game, true);
546+ }
547 } else {
548 it->set_location(game, warehouse_);
549 it->end_shipping(game);
550 *it = waiting_.back();
551 waiting_.pop_back();
552-
553- if (waiting_.empty())
554- set_need_ship(game, false);
555- }
556-}
557-
558-/**
559- * A ship has arrived at the dock. Clear all items designated for this dock,
560- * and load the ship.
561+ }
562+}
563+
564+/**
565+ * Receive shipping item from unloading ship.
566+ * Called by ship code.
567+ */
568+void PortDock::shipping_item_arrived(Game& game, ShippingItem& si) {
569+ si.set_location(game, warehouse_);
570+ si.end_shipping(game);
571+}
572+
573+/**
574+ * Receive shipping item from departing ship.
575+ * Called by ship code.
576+ */
577+void PortDock::shipping_item_returned(Game& game, ShippingItem& si) {
578+ si.set_location(game, this);
579+ waiting_.push_back(si);
580+}
581+
582+/**
583+ * A ship changed destination and is now coming to the dock. Increase counter for need_ship.
584+ */
585+void PortDock::ship_coming(bool affirmative) {
586+ if (affirmative) {
587+ ++ships_coming_;
588+ } else {
589+ --ships_coming_;
590+ }
591+}
592+
593+/**
594+ * A ship has arrived at the dock. Set its next destination and load it accordingly.
595 */
596 void PortDock::ship_arrived(Game& game, Ship& ship) {
597- std::vector<ShippingItem> items_brought_by_ship;
598- ship.withdraw_items(game, *this, items_brought_by_ship);
599-
600- for (ShippingItem& shipping_item : items_brought_by_ship) {
601- shipping_item.set_location(game, warehouse_);
602- shipping_item.end_shipping(game);
603- }
604+ ship_coming(false);
605
606 if (expedition_ready_) {
607 assert(expedition_bootstrap_ != nullptr);
608
609 // Only use an empty ship.
610 if (ship.get_nritems() < 1) {
611+ ship.set_destination(nullptr);
612 // Load the ship
613 std::vector<Worker*> workers;
614 std::vector<WareInstance*> wares;
615@@ -337,46 +364,61 @@
616 // The expedition goods are now on the ship, so from now on it is independent from the port
617 // and thus we switch the port to normal, so we could even start a new expedition,
618 cancel_expedition(game);
619- return fleet_->update(game);
620- }
621- }
622-
623- if (ship.get_nritems() < ship.descr().get_capacity() && !waiting_.empty()) {
624- uint32_t nrload =
625- std::min<uint32_t>(waiting_.size(), ship.descr().get_capacity() - ship.get_nritems());
626-
627- while (nrload--) {
628- // Check if the item has still a valid destination
629- if (waiting_.back().get_destination(game)) {
630- // Destination is valid, so we load the item onto the ship
631- ship.add_item(game, waiting_.back());
632- } else {
633- // The item has no valid destination anymore, so we just carry it
634- // back in the warehouse
635- waiting_.back().set_location(game, warehouse_);
636- waiting_.back().end_shipping(game);
637- }
638- waiting_.pop_back();
639- }
640-
641- if (waiting_.empty()) {
642- set_need_ship(game, false);
643- }
644- }
645-
646- fleet_->update(game);
647+ fleet_->update(game);
648+ return;
649+ }
650+ }
651+
652+ // Decide where the arrived ship will go next
653+ PortDock* next_port = fleet_->find_next_dest(game, ship, *this);
654+ if (!next_port) {
655+ ship.set_destination(next_port);
656+ return; // no need to load anything
657+ }
658+
659+ // Unload any wares/workers onboard the departing ship which are not favored by next dest
660+ ship.unload_unfit_items(game, *this, *next_port);
661+
662+ // Then load the remaining capacity of the departing ship with relevant items
663+ uint32_t remaining_capacity = ship.descr().get_capacity() - ship.get_nritems();
664+
665+ // Firstly load the items which go to chosen destination, while also checking for items with invalid destination
666+ for (auto si_it = waiting_.begin(); si_it != waiting_.end(); ++si_it) {
667+ const PortDock* itemdest = si_it->get_destination(game);
668+ if (itemdest) {
669+ // Valid destination. Only load the item if it matches the ship's destination and the ship still has room for it
670+ if (remaining_capacity > 0 && itemdest == next_port) {
671+ ship.add_item(game, *si_it); // load it
672+ si_it = waiting_.erase(si_it);
673+ --remaining_capacity;
674+ }
675+ } else {
676+ // Invalid destination. Carry the item back into the warehouse
677+ si_it->set_location(game, warehouse_);
678+ si_it->end_shipping(game);
679+ si_it = waiting_.erase(si_it);
680+ }
681+ }
682+
683+ if (remaining_capacity > 0) {
684+ // There is still come capacity left. Load any items favored by the chosen destination on the ship.
685+ for (auto si_it = waiting_.begin(); si_it != waiting_.end() && remaining_capacity > 0; ++si_it) {
686+ assert(si_it->get_destination(game) != nullptr);
687+ if (fleet_->is_path_favourable(*this, *next_port, *si_it->get_destination(game))) {
688+ ship.add_item(game, *si_it);
689+ si_it = waiting_.erase(si_it);
690+ --remaining_capacity;
691+ }
692+ }
693+ }
694+
695+ ship.set_destination(next_port);
696+ set_need_ship(game, !waiting_.empty());
697 }
698
699 void PortDock::set_need_ship(Game& game, bool need) {
700- molog("set_need_ship(%s)\n", need ? "true" : "false");
701-
702- if (need == need_ship_)
703- return;
704-
705- need_ship_ = need;
706-
707- if (fleet_) {
708- molog("... trigger fleet update\n");
709+ if (need && fleet_) {
710+ molog("trigger fleet update\n");
711 fleet_->update(game);
712 }
713 }
714@@ -445,13 +487,13 @@
715
716 if (warehouse_) {
717 Coords pos(warehouse_->get_position());
718- molog("PortDock for warehouse %u (at %i,%i) in fleet %u, need_ship: %s, waiting: %" PRIuS
719+ molog("PortDock for warehouse %u (at %i,%i) in fleet %u, expedition_ready: %s, waiting: %" PRIuS
720 "\n",
721 warehouse_->serial(), pos.x, pos.y, fleet_ ? fleet_->serial() : 0,
722- need_ship_ ? "true" : "false", waiting_.size());
723+ expedition_ready_ ? "true" : "false", waiting_.size());
724 } else {
725- molog("PortDock without a warehouse in fleet %u, need_ship: %s, waiting: %" PRIuS "\n",
726- fleet_ ? fleet_->serial() : 0, need_ship_ ? "true" : "false", waiting_.size());
727+ molog("PortDock without a warehouse in fleet %u, expedition_ready: %s, waiting: %" PRIuS "\n",
728+ fleet_ ? fleet_->serial() : 0, expedition_ready_ ? "true" : "false", waiting_.size());
729 }
730
731 for (const ShippingItem& shipping_item : waiting_) {
732@@ -460,12 +502,12 @@
733 }
734 }
735
736-constexpr uint8_t kCurrentPacketVersion = 3;
737+constexpr uint8_t kCurrentPacketVersion = 4;
738
739 PortDock::Loader::Loader() : warehouse_(0) {
740 }
741
742-void PortDock::Loader::load(FileRead& fr) {
743+void PortDock::Loader::load(FileRead& fr, uint8_t packet_version) {
744 PlayerImmovable::Loader::load(fr);
745
746 PortDock& pd = get<PortDock>();
747@@ -479,7 +521,18 @@
748 pd.set_position(egbase(), pd.dockpoints_[i]);
749 }
750
751- pd.need_ship_ = fr.unsigned_8();
752+ pd.ships_coming_ = fr.unsigned_8();
753+
754+ // TODO(GunChleoc): Savegame compatibility Build 20
755+ if (packet_version < 4) {
756+ pd.ships_coming_ = 0;
757+ for (const Serial ship_serial : pd.owner().ships()) {
758+ Ship* ship = dynamic_cast<Ship*>(egbase().objects().get_object(ship_serial));
759+ if (ship->get_destination(egbase())->serial() == pd.serial()) {
760+ ++pd.ships_coming_;
761+ }
762+ }
763+ }
764
765 waiting_.resize(fr.unsigned_32());
766 for (ShippingItem::Loader& shipping_loader : waiting_) {
767@@ -499,10 +552,10 @@
768 PortDock& pd = get<PortDock>();
769 pd.warehouse_ = &mol().get<Warehouse>(warehouse_);
770
771- pd.waiting_.resize(waiting_.size());
772 for (uint32_t i = 0; i < waiting_.size(); ++i) {
773- pd.waiting_[i] = waiting_[i].get(mol());
774+ pd.waiting_.push_back(waiting_[i].get(mol()));
775 }
776+ assert(pd.waiting_.size() == waiting_.size());
777 }
778
779 void PortDock::Loader::load_finish() {
780@@ -527,10 +580,11 @@
781 try {
782 // The header has been peeled away by the caller
783
784+ // TODO(GunChleoc): Savegame compatibility Build 20
785 uint8_t const packet_version = fr.unsigned_8();
786- if (packet_version == kCurrentPacketVersion) {
787+ if (packet_version >= 3 && packet_version <= kCurrentPacketVersion) {
788 loader->init(egbase, mol, *new PortDock(nullptr));
789- loader->load(fr);
790+ loader->load(fr, packet_version);
791 } else {
792 throw UnhandledVersionError("PortDock", packet_version, kCurrentPacketVersion);
793 }
794@@ -553,7 +607,7 @@
795 write_coords_32(&fw, coords);
796 }
797
798- fw.unsigned_8(need_ship_);
799+ fw.unsigned_8(ships_coming_);
800
801 fw.unsigned_32(waiting_.size());
802 for (ShippingItem& shipping_item : waiting_) {
803
804=== modified file 'src/economy/portdock.h'
805--- src/economy/portdock.h 2018-09-04 15:48:47 +0000
806+++ src/economy/portdock.h 2018-09-21 19:37:16 +0000
807@@ -20,6 +20,7 @@
808 #ifndef WL_ECONOMY_PORTDOCK_H
809 #define WL_ECONOMY_PORTDOCK_H
810
811+#include <list>
812 #include <memory>
813
814 #include "base/macros.h"
815@@ -85,9 +86,7 @@
816 return fleet_;
817 }
818 PortDock* get_dock(Flag& flag) const;
819- bool get_need_ship() const {
820- return need_ship_ || expedition_ready_;
821- }
822+ uint32_t get_need_ship() const;
823
824 void set_economy(Economy*) override;
825
826@@ -113,6 +112,9 @@
827 void add_shippingitem(Game&, Worker&);
828 void update_shippingitem(Game&, Worker&);
829
830+ void shipping_item_arrived(Game&, ShippingItem&);
831+ void shipping_item_returned(Game&, ShippingItem&);
832+ void ship_coming(bool affirmative);
833 void ship_arrived(Game&, Ship&);
834
835 void log_general_info(const EditorGameBase&) const override;
836@@ -139,14 +141,14 @@
837
838 void init_fleet(EditorGameBase& egbase);
839 void set_fleet(Fleet* fleet);
840- void update_shippingitem(Game&, std::vector<ShippingItem>::iterator);
841+ void update_shippingitem(Game&, std::list<ShippingItem>::iterator);
842 void set_need_ship(Game&, bool need);
843
844 Fleet* fleet_;
845 Warehouse* warehouse_;
846 PositionList dockpoints_;
847- std::vector<ShippingItem> waiting_;
848- bool need_ship_;
849+ std::list<ShippingItem> waiting_;
850+ uint8_t ships_coming_;
851 bool expedition_ready_;
852
853 std::unique_ptr<ExpeditionBootstrap> expedition_bootstrap_;
854@@ -157,7 +159,7 @@
855 public:
856 Loader();
857
858- void load(FileRead&);
859+ void load(FileRead&, uint8_t packet_version);
860 void load_pointers() override;
861 void load_finish() override;
862
863
864=== modified file 'src/economy/shippingitem.cc'
865--- src/economy/shippingitem.cc 2018-04-07 16:59:00 +0000
866+++ src/economy/shippingitem.cc 2018-09-21 19:37:16 +0000
867@@ -105,7 +105,7 @@
868 worker->end_shipping(game);
869 }
870
871-PortDock* ShippingItem::get_destination(Game& game) {
872+const PortDock* ShippingItem::get_destination(Game& game) const {
873 return destination_dock_.get(game);
874 }
875
876
877=== modified file 'src/economy/shippingitem.h'
878--- src/economy/shippingitem.h 2018-04-07 16:59:00 +0000
879+++ src/economy/shippingitem.h 2018-09-21 19:37:16 +0000
880@@ -53,7 +53,7 @@
881 void get(const EditorGameBase& game, WareInstance** ware, Worker** worker) const;
882
883 void set_economy(Game&, Economy* e);
884- PortDock* get_destination(Game&);
885+ const PortDock* get_destination(Game&) const;
886
887 void remove(EditorGameBase&);
888
889
890=== modified file 'src/logic/map_objects/tribes/ship.cc'
891--- src/logic/map_objects/tribes/ship.cc 2018-09-11 16:58:16 +0000
892+++ src/logic/map_objects/tribes/ship.cc 2018-09-21 19:37:16 +0000
893@@ -303,12 +303,22 @@
894
895 FCoords position = map.get_fcoords(get_position());
896 if (position.field->get_immovable() == dst) {
897- molog("ship_update: Arrived at dock %u\n", dst->serial());
898- lastdock_ = dst;
899- destination_ = nullptr;
900- dst->ship_arrived(game, *this);
901- start_task_idle(game, descr().main_animation(), 250);
902- Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged));
903+ if (lastdock_ != dst) {
904+ molog("ship_update: Arrived at dock %u\n", dst->serial());
905+ lastdock_ = dst;
906+ }
907+ if (withdraw_item(game, *dst)) {
908+ schedule_act(game, kShipInterval);
909+ return true;
910+ }
911+
912+ dst->ship_arrived(game, *this); // This will also set the destination
913+ dst = get_destination(game);
914+ if (dst) {
915+ start_task_movetodock(game, *dst);
916+ } else {
917+ start_task_idle(game, descr().main_animation(), 250);
918+ }
919 return true;
920 }
921
922@@ -484,7 +494,7 @@
923 }
924
925 if (totalprob == 0) {
926- start_task_idle(game, descr().main_animation(), 1500);
927+ start_task_idle(game, descr().main_animation(), kShipInterval);
928 return;
929 }
930
931@@ -496,13 +506,13 @@
932 }
933
934 if (dir == 0 || dir > LAST_DIRECTION) {
935- start_task_idle(game, descr().main_animation(), 1500);
936+ start_task_idle(game, descr().main_animation(), kShipInterval);
937 return;
938 }
939
940 FCoords neighbour = map.get_neighbour(position, dir);
941 if (!(neighbour.field->nodecaps() & MOVECAPS_SWIM)) {
942- start_task_idle(game, descr().main_animation(), 1500);
943+ start_task_idle(game, descr().main_animation(), kShipInterval);
944 return;
945 }
946
947@@ -542,7 +552,7 @@
948 pgettext("ship", "Waiting"), _("Island Circumnavigated"),
949 _("An expedition ship sailed around its island without any events."),
950 "images/wui/ship/ship_explore_island_cw.png");
951- return start_task_idle(game, descr().main_animation(), 1500);
952+ return start_task_idle(game, descr().main_animation(), kShipInterval);
953 }
954 }
955 // The ship is supposed to follow the coast as close as possible, therefore the check
956@@ -585,7 +595,7 @@
957 shipname_.c_str());
958 set_ship_state_and_notify(
959 ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand);
960- start_task_idle(game, descr().main_animation(), 1500);
961+ start_task_idle(game, descr().main_animation(), kShipInterval);
962 return;
963 }
964 } else { // scouting towards a specific direction
965@@ -598,7 +608,7 @@
966 // coast reached
967 set_ship_state_and_notify(
968 ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand);
969- start_task_idle(game, descr().main_animation(), 1500);
970+ start_task_idle(game, descr().main_animation(), kShipInterval);
971 // Send a message to the player, that a new coast was reached
972 send_message(game,
973 /** TRANSLATORS: A ship has discovered land */
974@@ -692,14 +702,14 @@
975 }
976
977 expedition_.reset(nullptr);
978- return start_task_idle(game, descr().main_animation(), 1500);
979+ return start_task_idle(game, descr().main_animation(), kShipInterval);
980 }
981 }
982 FALLS_THROUGH;
983 case ShipStates::kExpeditionWaiting:
984 case ShipStates::kExpeditionPortspaceFound: {
985 // wait for input
986- start_task_idle(game, descr().main_animation(), 1500);
987+ start_task_idle(game, descr().main_animation(), kShipInterval);
988 return;
989 }
990 FALLS_THROUGH;
991@@ -729,14 +739,17 @@
992
993 /**
994 * Enter a new destination port for the ship.
995- *
996- * @note This is supposed to be called only from the scheduling code of @ref Fleet.
997+ * Call this after (un)loading the ship, for proper logging.
998 */
999-void Ship::set_destination(Game& game, PortDock& pd) {
1000- molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd.serial(),
1001- items_.size());
1002- destination_ = &pd;
1003- send_signal(game, "wakeup");
1004+void Ship::set_destination(PortDock* pd) {
1005+ destination_ = pd;
1006+ if (pd) {
1007+ molog("set_destination / sending to portdock %u (carrying %" PRIuS " items)\n", pd->serial(),
1008+ items_.size());
1009+ pd->ship_coming(true);
1010+ } else {
1011+ molog("set_destination / none\n");
1012+ }
1013 Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged));
1014 }
1015
1016@@ -747,14 +760,39 @@
1017 items_.back().set_location(game, this);
1018 }
1019
1020-void Ship::withdraw_items(Game& game, PortDock& pd, std::vector<ShippingItem>& items) {
1021- uint32_t dst = 0;
1022- for (uint32_t src = 0; src < items_.size(); ++src) {
1023- PortDock* destination = items_[src].get_destination(game);
1024- if (!destination || destination == &pd) {
1025- items.push_back(items_[src]);
1026+/**
1027+ * Unload one item designated for given dock or for no dock.
1028+ * \return true if item unloaded.
1029+ */
1030+bool Ship::withdraw_item(Game& game, PortDock& pd) {
1031+ bool unloaded = false;
1032+ size_t dst = 0;
1033+ for (ShippingItem& si : items_) {
1034+ if (!unloaded) {
1035+ const PortDock* itemdest = si.get_destination(game);
1036+ if (!itemdest || itemdest == &pd) {
1037+ pd.shipping_item_arrived(game, si);
1038+ unloaded = true;
1039+ continue;
1040+ }
1041+ }
1042+ items_[dst++] = si;
1043+ }
1044+ items_.resize(dst);
1045+ return unloaded;
1046+}
1047+
1048+/**
1049+ * Unload all items not favored by given next dest.
1050+ * Assert all items for current portdock have already been unloaded.
1051+ */
1052+void Ship::unload_unfit_items(Game& game, PortDock& here, const PortDock& nextdest) {
1053+ size_t dst = 0;
1054+ for (ShippingItem& si : items_) {
1055+ if (fleet_->is_path_favourable(here, nextdest, *si.get_destination(game))) {
1056+ items_[dst++] = si;
1057 } else {
1058- items_[dst++] = items_[src];
1059+ here.shipping_item_returned(game, si);
1060 }
1061 }
1062 items_.resize(dst);
1063@@ -813,7 +851,7 @@
1064 // I (tiborb) failed to invoke this situation when testing so
1065 // I am not sure if following line behaves allright
1066 get_fleet()->update(game);
1067- start_task_idle(game, descr().main_animation(), 5000);
1068+ start_task_idle(game, descr().main_animation(), kFleetInterval);
1069 }
1070 }
1071
1072@@ -837,10 +875,10 @@
1073
1074 set_economy(game, expedition_->economy);
1075
1076- for (int i = items_.size() - 1; i >= 0; --i) {
1077+ for (const ShippingItem& si : items_) {
1078 WareInstance* ware;
1079 Worker* worker;
1080- items_.at(i).get(game, &ware, &worker);
1081+ si.get(game, &ware, &worker);
1082 if (worker) {
1083 worker->reset_tasks(game);
1084 worker->start_task_idle(game, 0, -1);
1085@@ -971,8 +1009,12 @@
1086 /// @note only called via player command
1087 void Ship::sink_ship(Game& game) {
1088 // Running colonization has the highest priority + a sink request is only valid once
1089- if (!state_is_sinkable())
1090+ if (!state_is_sinkable()) {
1091 return;
1092+ }
1093+ if (destination_.is_set()) {
1094+ destination_.get(game)->ship_coming(false);
1095+ }
1096 ship_state_ = ShipStates::kSinkRequest;
1097 // Make sure the ship is active and close possible open windows
1098 ship_wakeup(game);
1099
1100=== modified file 'src/logic/map_objects/tribes/ship.h'
1101--- src/logic/map_objects/tribes/ship.h 2018-09-05 06:42:21 +0000
1102+++ src/logic/map_objects/tribes/ship.h 2018-09-21 19:37:16 +0000
1103@@ -78,6 +78,8 @@
1104 DISALLOW_COPY_AND_ASSIGN(ShipDescr);
1105 };
1106
1107+constexpr int32_t kShipInterval = 1500;
1108+
1109 /**
1110 * Ships belong to a player and to an economy. The usually are in a (unique)
1111 * fleet for a player, but only if they are on standard duty. Exploration ships
1112@@ -104,7 +106,7 @@
1113 return economy_;
1114 }
1115 void set_economy(Game&, Economy* e);
1116- void set_destination(Game&, PortDock&);
1117+ void set_destination(PortDock*);
1118
1119 void init_auto_task(Game&) override;
1120
1121@@ -126,8 +128,9 @@
1122 return items_[idx];
1123 }
1124
1125- void withdraw_items(Game& game, PortDock& pd, std::vector<ShippingItem>& items);
1126- void add_item(Game&, const ShippingItem& item);
1127+ void add_item(Game&, const ShippingItem&);
1128+ bool withdraw_item(Game&, PortDock&);
1129+ void unload_unfit_items(Game&, PortDock& here, const PortDock& nextdest);
1130
1131 // A ship with task expedition can be in four states: kExpeditionWaiting, kExpeditionScouting,
1132 // kExpeditionPortspaceFound or kExpeditionColonizing in the first states, the owning player of

Subscribers

People subscribed via source and target branches

to status/vote changes: