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