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

Proposed by TiborB
Status: Merged
Merged at revision: 7695
Proposed branch: lp:~widelands-dev/widelands/request_supply_opt
Merge into: lp:widelands
Diff against target: 314 lines (+123/-18)
8 files modified
src/economy/economy.cc (+76/-18)
src/economy/economy.h (+20/-0)
src/economy/idleworkersupply.cc (+5/-0)
src/economy/idleworkersupply.h (+1/-0)
src/economy/supply.h (+8/-0)
src/economy/ware_instance.cc (+11/-0)
src/economy/warehousesupply.h (+1/-0)
src/logic/warehouse.cc (+1/-0)
To merge this branch: bzr merge lp:~widelands-dev/widelands/request_supply_opt
Reviewer Review Type Date Requested Status
SirVer Approve
Tino Approve
Review via email: mp+280193@code.launchpad.net

Description of the change

This is small redesign of delivery of wares to requestors.
1. Engine now evenly distributes wares to all warehouses that are preferred for this type of ware (in other words, ware goes to a warehouse with lower stock of the material/worker)
2. Engine now test supplies in order starting from nearest one. This itself should lead to lesser computation. Additionaly I implemented a treshold (now 5) that test only 5 nearest supplies. The goal is to reduce CPU usage, especially on big maps. Routing is very expensive on long maps.

This was discussed on forum and must be yet considered.

To post a comment you must log in.
Revision history for this message
GunChleoc (gunchleoc) wrote :

Forum thread: https://wl.widelands.org/forum/topic/1856/

We still need to think about the threshold, but the rest of the code LGTM :)

This still needs testing, but I think we can wait with that until we have decided how to solve this.

Revision history for this message
TiborB (tiborb95) wrote :

Resubmit.

I removed controversial treshold, and added two features
a) if more wares (of the same type) on one flag, only one ware is considered
b) wares on ships are ignored. Routing is deceived because it considers port's flag as a actual location of ware/worker.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Hi, I am bunnybot (https://github.com/widelands/bunnybot).

I am keeping the source branch lp:~widelands-dev/widelands/request_supply_opt mirrored to
  https://github.com/widelands/widelands/tree/_widelands_dev_widelands_request_supply_opt

The latest continuous integration build can always be found here:
  https://travis-ci.org/widelands/widelands/branches
Please do not merge without making sure that it passes.

You can give me commands by starting a line with @bunnybot <command>. I understand:
 merge: Merges the source branch into the target branch, closing the pull request.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Travis build 119 has changed state to: failed. Details: https://travis-ci.org/widelands/widelands/builds/99743957.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Travis build 162 has changed state to: created. Details: https://travis-ci.org/widelands/widelands/builds/99874912.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Travis build 162 has changed state to: started. Details: https://travis-ci.org/widelands/widelands/builds/99874912.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Travis build 162 has changed state to: errored. Details: https://travis-ci.org/widelands/widelands/builds/99874912.

Revision history for this message
Tino (tino79) wrote :

LGTM.
I cannot really quantify the performance gain, but i did not find any regression.

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

Travis build 162 has changed state to: errored. Details: https://travis-ci.org/widelands/widelands/builds/99874912.

Revision history for this message
SirVer (sirver) wrote :

I think this is broken in the current state. The problem is that logic depends on available_supply which is a multimap. The problem is that the map is sorted by distance, then by a pointer.

In a replay or in a multiplayer games, the order of elements in this map is different, yielding therefore different routing results. This will desync the game.

Change the ordering to use a tie-breaker, for example the game object id before the pointer.

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

Travis build 162 has changed state to: passed. Details: https://travis-ci.org/widelands/widelands/builds/99874912.

Revision history for this message
TiborB (tiborb95) wrote :

@SirVer

What about:

std::multimap<<uint32_t,uint32_t>, Supply*> available_supplies;

Where <uint32_t,int32_t> is <distance, serial>, serial should be the same across the network clients, I presume...

Revision history for this message
SirVer (sirver) wrote :

That should work - but I would suggest to make a tiny private struct to improve readability.

struct SupplyQuality {
   uint32 distance;
   uint32 serial;

   bool operator<(const SupplyQuality& other) const {
       return std::forward_as_tuple(distance, serial) < std::forward_as_tuple(other.distance, other.serial);
   }
}

Revision history for this message
TiborB (tiborb95) wrote :

Well I am not that skillful in C++, I want(ed) to use trivial:

bool operator() (const uint32_t & p1, const uint32_t & p2) const {
 return p1.distance == p2.distance ? p1.serial < p2.serial : p1.distance < p2.distance;
}

(not tested of course)

Revision history for this message
TiborB (tiborb95) wrote :

Oh, I just read about comparison of tuples, so I understand your example now, thanks..

Revision history for this message
TiborB (tiborb95) wrote :

@SirVer

I reworked it and testing it. I am not satisfied with performance though. Perhaps you know that I proposed a treshold (f.e. 5) that would check only 5 nearest (as crow fly) wares and pick a one from them. This could improve performance, but was rejected and currently is not in the code.

However I have another suggestion - let revive the treshold and X nearest wares are checked all, and from other wares only the ones that are in the warehouses. It used to be this way before AFAIK. I mean only wares in warehouses were used for delivery.

I currently test 512x512 map and after 5 hours it can barely run at 2x speed. I dont say that the routing is culprit but I believe it has its share...

I have not pushed new code yet.

Revision history for this message
SirVer (sirver) wrote :

[threshold]
I did not follow the discussion about the threshold though, on a quick thought I can see downsides to this approach. I suggest doing more timing analysis (with AI disabled) to figure out what actually takes time here and if there are ways of improving the situation.

> I dont say that the routing is culprit but I believe it has its share...

you have to proof that to make me believe it :). In my benchmarks about timing with the drawing code, AI is always dwarfing everything else in profiles, especially in longer games - but I rarely look at very large maps, so I'd need to proof what actually takes the most time too.

If you feel this approach is an improvement, how about we merge it soonish and then go back and try timing a large map with AI disabled to see what actually eats cycles.

Revision history for this message
TiborB (tiborb95) wrote :

So I made a bit of profiling, first of all it seems the game (drawing itself) takes quite a lot of CPU time - perhaps problem on my side? Tests run for 3 minutes of gameplay, but loading took also quite a bit of time (not counted in 3 minutes)....

But results:
I compared
1. trunk + crater (small map after 45 min)
2. trunk + Cristobals sea (512x512 after 4 hours)
3. request_supply_opt + Cristobals sea (512x512 after 4 hours)

I used savegames...

I picked only 3 functions here, look at second column (cummulative %)

trunk+crater:
[79] 1.8 0.00 0.17 4530 DefaultAI::think() [79]
[90] 1.6 0.00 0.14 37761 Widelands::Economy::_find_best_supply()
[94] 1.6 0.00 0.14 5494 Widelands::Economy::find_route()

trunk + big map (Cristobals sea)
[16] 22.8 0.00 8.43 105284 Widelands::Economy::find_route()
[24] 17.5 0.26 6.20 1145776 Widelands::Economy::_find_best_supply() [24]
[65] 3.8 0.02 1.38 22120 DefaultAI::think()

this branch + big map (Cristobals sea)
[20] 15.7 0.00 5.76 112254 Widelands::Economy::find_route()
[24] 14.8 0.39 5.06 1302844 Widelands::Economy::_find_best_supply()
[61] 4.3 0.00 1.59 33740 DefaultAI::think() [61]

My conclusions:
On big maps, _find_best_supply takes significant portion of CPU time, many fold then AI.
My changes as they are now, brings reduction about 20% of _find_best_supply and about 35% of find_route() + on big maps.

I believe implementing a treshold would help to further reduction.....

Revision history for this message
SirVer (sirver) wrote :

> So I made a bit of profiling, first of all it seems the game (drawing itself) takes quite a lot of CPU time - perhaps problem on my side?

No, that is accurate. The render_queue branch makes this already better, but only a full texture_atlas will make this really fast. I picked up working on this today again.

The threshold will at most cut the time in half for find_best_supply if i understood correctly. How much will that actually buy us? I suggest getting the change in as is without the threshold - as it should be correct in all cases and then experiment on threshold or even completely different approaches. Let us know if you think it is ready for another look.

Revision history for this message
TiborB (tiborb95) wrote :

> The threshold will at most cut the time in half for find_best_supply if i understood correctly.

I dont think there is mathematical reason for 'half' but if it was half it would be great....

It depends on number of supplies - if supplies count<=treshold - no change at all. Or rather - savings depend on the size of player's teritorry/economy....

I did some changes today so you can look at the code again now

Revision history for this message
SirVer (sirver) wrote :

code lgtm now. A couple of minor nits, feel free to merge after addressing these.

review: Approve
Revision history for this message
TiborB (tiborb95) wrote :

Added a comment to your comment, see in diff

Revision history for this message
TiborB (tiborb95) wrote :

@SirVer

I incorporated your comments, with one exception, can I merge it?

Revision history for this message
SirVer (sirver) wrote :

Tibor, sure, go ahead and merge.

> I looked at other examples f.e. here: http://bazaar.launchpad.net/~widelands-dev/widelands/request_supply_opt/view/head:/src/economy/supply.h#L94 and everywhere it is done this way.

Ah, that is a philosophical question, really. Does local style trump global style? Should better paradigms be adapted peu-a-peu or in one big refactoring? There are many school of thoughts here. I think once you identified a better approach, you should start using it for new code and fix old code over time. Other people think that you should keep the old ways until you can change a full code base to new ways.

So there are really two questions for you here:

1) do you agree that passing mutable values by pointer is better than passing them by non-const reference? I find foo(&game) vs foo(game) a clear advantage for readability, so I'd say yes. This also follows the google style guide which I find a great reference of how and why to do things anyways. But you might disagree and I am in no position to enforce a 'true' widelands code style.

2) do you agree that new code should adopt new paradigms immediately at the cost of inconsistency in the code base or not?

As said, I answer yes to both questions, so I'd change the code to using a pointer. But it is entirely your call. I will not think any worse of you if you decide against changing the code :)

> Also note the 'const' at the end of line, does it not guarantee immutability of Game?

no, the const at the end of the line means that the object represented by 'this' cannot change, but does not say anything about the arguments of the method - they can change.

Revision history for this message
TiborB (tiborb95) wrote :

You are right, I will rework it, though I am not that good programmer so it is not as simple for me as for you. This is only reason why I opted to mimic the existing code.
But as there is no need to hurry with this branch so it can wait a bit again

Revision history for this message
GunChleoc (gunchleoc) wrote :

Changing a reference to a pointer can be a bit of a pain, but it isn't complex - the compiler will tell you if anything goes wrong, so you can't mess it up. What you need to do is change foo.bar to foo->bar within the function, and add & or * in front of foo when calling the function - I can never remember which symbol to use here, but the compiler will tell me :)

Revision history for this message
TiborB (tiborb95) wrote :

reworked, tested (not long time). Can you please look at it once more?

Revision history for this message
SirVer (sirver) wrote :

there are still NOCOM in the diff which need removing, otherwise lgtm.

review: Approve
Revision history for this message
TiborB (tiborb95) wrote :

Thanks!

@bunnybot merge

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/economy/economy.cc'
2--- src/economy/economy.cc 2015-11-11 09:52:55 +0000
3+++ src/economy/economy.cc 2016-01-07 18:40:19 +0000
4@@ -20,6 +20,7 @@
5 #include "economy/economy.h"
6
7 #include <memory>
8+#include <unordered_set>
9
10 #include <boost/bind.hpp>
11
12@@ -664,14 +665,40 @@
13 Route * best_route = nullptr;
14 int32_t best_cost = -1;
15 Flag & target_flag = req.target_flag();
16+ Map & map = game.map();
17+
18+ available_supplies.clear();
19
20 for (size_t i = 0; i < m_supplies.get_nrsupplies(); ++i) {
21 Supply & supp = m_supplies[i];
22
23- // Check requirements
24+ // Just skip if supply does not provide required ware
25 if (!supp.nr_supplies(game, req))
26 continue;
27
28+ const SupplyProviders provider = supp.provider_type(&game);
29+
30+ // We generally ignore disponible wares on ship as it is not possible to reliably
31+ // calculate route (transportation time)
32+ if (provider == SupplyProviders::kShip) {
33+ continue;
34+ }
35+
36+ const Widelands::Coords provider_position = supp.get_position(game)->base_flag().get_position();
37+
38+ const uint32_t dist = map.calc_distance(target_flag.get_position(), provider_position);
39+
40+ UniqueDistance ud = {dist, supp.get_position(game)->serial(), provider};
41+
42+ // std::map quarantees uniqueness, practically it means that if more wares are on the same flag, only
43+ // first one will be inserted into available_supplies
44+ available_supplies.insert(std::make_pair(ud, &m_supplies[i]));
45+ }
46+
47+ // Now available supplies have been sorted by distance to requestor
48+ for (auto& supplypair : available_supplies) {
49+ Supply & supp = *supplypair.second;
50+
51 Route * const route =
52 best_route != &buf_route0 ? &buf_route0 : &buf_route1;
53 // will be cleared by find_route()
54@@ -685,11 +712,17 @@
55 req.get_type(),
56 best_cost))
57 {
58- if (!best_route)
59- log ("Economy::find_best_supply: Error, COULD NOT FIND A ROUTE!");
60+ if (!best_route) {
61+ log ("Economy::find_best_supply: Error, COULD NOT FIND A ROUTE!");
62+ // To help to debug this a bit:
63+ log (" ... ware at: %3dx%3d, requestor at: %3dx%3d!",
64+ supp.get_position(game)->base_flag().get_position().x,
65+ supp.get_position(game)->base_flag().get_position().y,
66+ target_flag.get_position().x,
67+ target_flag.get_position().y);
68+ }
69 continue;
70 }
71-
72 best_supply = &supp;
73 best_route = route;
74 best_cost = route->get_totalcost();
75@@ -776,8 +809,9 @@
76 }
77
78 int32_t const priority = req.get_priority(cost);
79- if (priority < 0)
80+ if (priority < 0) {
81 continue;
82+ }
83
84 // Otherwise, consider this request/supply pair for queueing
85 RequestSupplyPair rsp;
86@@ -825,8 +859,9 @@
87 rsps.nexttimer = 200;
88 }
89
90- if (rsps.nexttimer > 0) // restart the timer, if necessary
91+ if (rsps.nexttimer > 0) { // restart the timer, if necessary
92 _start_request_timer(rsps.nexttimer);
93+ }
94 }
95
96
97@@ -1037,12 +1072,30 @@
98
99 bool haveprefer = false;
100 bool havenormal = false;
101+
102+ // One of preferred warehouses (if any) with lowest stock of ware/worker
103+ Warehouse * preferred_wh = nullptr;
104+ // Stock of particular ware in preferred warehouse
105+ uint32_t preferred_wh_stock = std::numeric_limits<uint32_t>::max();
106+
107 for (uint32_t nwh = 0; nwh < m_warehouses.size(); ++nwh) {
108 Warehouse * wh = m_warehouses[nwh];
109 Warehouse::StockPolicy policy = wh->get_stock_policy(type, ware);
110 if (policy == Warehouse::SP_Prefer) {
111 haveprefer = true;
112- break;
113+
114+ // Getting count of worker/ware
115+ uint32_t current_stock;
116+ if (type == WareWorker::wwWARE) {
117+ current_stock = wh->get_wares().stock(ware);
118+ } else {
119+ current_stock = wh->get_workers().stock(ware);
120+ }
121+ // Stocks lower then in previous one?
122+ if (current_stock < preferred_wh_stock) {
123+ preferred_wh = wh;
124+ preferred_wh_stock = current_stock;
125+ }
126 }
127 if (policy == Warehouse::SP_Normal)
128 havenormal = true;
129@@ -1050,17 +1103,22 @@
130 if (!havenormal && !haveprefer && type == wwWARE)
131 continue;
132
133- Warehouse * wh = find_closest_warehouse
134- (supply.get_position(game)->base_flag(), type, nullptr, 0,
135- (!haveprefer && !havenormal)
136- ?
137- WarehouseAcceptFn()
138- :
139- boost::bind
140- (&accept_warehouse_if_policy,
141- _1, type, ware,
142- haveprefer ? Warehouse::SP_Prefer : Warehouse::SP_Normal));
143-
144+ // We either have one preferred warehouse picked up or walk on roads to find nearest one
145+ Warehouse * wh = nullptr;
146+ if (preferred_wh) {
147+ wh = preferred_wh;
148+ } else {
149+ wh = find_closest_warehouse
150+ (supply.get_position(game)->base_flag(), type, nullptr, 0,
151+ (!havenormal)
152+ ?
153+ WarehouseAcceptFn()
154+ :
155+ boost::bind
156+ (&accept_warehouse_if_policy,
157+ _1, type, ware,
158+ Warehouse::SP_Normal));
159+ }
160 if (!wh) {
161 log
162 ("Warning: Economy::_handle_active_supplies "
163
164=== modified file 'src/economy/economy.h'
165--- src/economy/economy.h 2015-11-11 09:52:55 +0000
166+++ src/economy/economy.h 2016-01-07 18:40:19 +0000
167@@ -31,6 +31,7 @@
168 #include "logic/instances.h"
169 #include "logic/warelist.h"
170 #include "logic/wareworker.h"
171+#include "economy/supply.h"
172 #include "economy/supply_list.h"
173 #include "ui_basic/unique_window.h"
174
175@@ -184,6 +185,22 @@
176 void rebalance_supply() {_start_request_timer();}
177
178 private:
179+
180+ // This structs is to store distance from supply to request(or), but to allow unambiguous
181+ // sorting if distances are the same, we use also serial number of provider and type of provider (flag,
182+ // warehouse)
183+ struct UniqueDistance {
184+ bool operator<(const UniqueDistance& other) const {
185+ return std::forward_as_tuple(distance, serial, provider_type)
186+ <
187+ std::forward_as_tuple(other.distance, other.serial, other.provider_type);
188+ }
189+
190+ uint32_t distance;
191+ uint32_t serial;
192+ SupplyProviders provider_type;
193+ };
194+
195 /*************/
196 /* Functions */
197 /*************/
198@@ -237,6 +254,9 @@
199 static std::unique_ptr<Soldier> m_soldier_prototype;
200 UI::UniqueWindow::Registry m_optionswindow_registry;
201
202+ // 'list' of unique providers
203+ std::map<UniqueDistance, Supply*> available_supplies;
204+
205 DISALLOW_COPY_AND_ASSIGN(Economy);
206 };
207
208
209=== modified file 'src/economy/idleworkersupply.cc'
210--- src/economy/idleworkersupply.cc 2015-11-11 09:52:55 +0000
211+++ src/economy/idleworkersupply.cc 2016-01-07 18:40:19 +0000
212@@ -71,6 +71,11 @@
213 return true;
214 }
215
216+SupplyProviders IdleWorkerSupply::provider_type(Game *) const
217+{
218+ return SupplyProviders::kFlagOrRoad;
219+}
220+
221 bool IdleWorkerSupply::has_storage() const
222 {
223 return m_worker.get_transfer();
224
225=== modified file 'src/economy/idleworkersupply.h'
226--- src/economy/idleworkersupply.h 2015-11-11 09:52:55 +0000
227+++ src/economy/idleworkersupply.h 2016-01-07 18:40:19 +0000
228@@ -34,6 +34,7 @@
229 PlayerImmovable * get_position(Game &) override;
230
231 bool is_active() const override;
232+ SupplyProviders provider_type(Game *) const override;
233 bool has_storage() const override;
234 void get_ware_type(WareWorker & type, DescriptionIndex & ware) const override;
235 void send_to_storage(Game &, Warehouse * wh) override;
236
237=== modified file 'src/economy/supply.h'
238--- src/economy/supply.h 2015-11-11 09:52:55 +0000
239+++ src/economy/supply.h 2016-01-07 18:40:19 +0000
240@@ -33,6 +33,8 @@
241 class WareInstance;
242 class Worker;
243
244+enum class SupplyProviders {kWarehouse, kFlagOrRoad, kShip};
245+
246 /**
247 * A Supply is a virtual base class representing something that can offer
248 * wares of any type for any purpose.
249@@ -57,6 +59,12 @@
250 virtual bool is_active() const = 0;
251
252 /**
253+ * Return the type of player im/movable where the ware is now (warehouse,
254+ * flag or ship).
255+ */
256+ virtual SupplyProviders provider_type(Game *) const = 0;
257+
258+ /**
259 * Indicates whether this supply is in storage or on its way to
260 * storage.
261 *
262
263=== modified file 'src/economy/ware_instance.cc'
264--- src/economy/ware_instance.cc 2015-12-28 10:08:39 +0000
265+++ src/economy/ware_instance.cc 2016-01-07 18:40:19 +0000
266@@ -55,6 +55,7 @@
267 // implementation of Supply
268 PlayerImmovable * get_position(Game &) override;
269 bool is_active() const override;
270+ SupplyProviders provider_type(Game *) const override;
271 bool has_storage() const override;
272 void get_ware_type(WareWorker & type, DescriptionIndex & ware) const override;
273 void send_to_storage(Game &, Warehouse * wh) override;
274@@ -126,6 +127,16 @@
275 return true;
276 }
277
278+SupplyProviders IdleWareSupply::provider_type(Game* game) const
279+{
280+ MapObject * const loc = m_ware.get_location(*game);
281+ if (is_a(Ship, loc)) {
282+ return SupplyProviders::kShip;
283+ }
284+
285+ return SupplyProviders::kFlagOrRoad;
286+}
287+
288 bool IdleWareSupply::has_storage() const
289 {
290 return m_ware.is_moving();
291
292=== modified file 'src/economy/warehousesupply.h'
293--- src/economy/warehousesupply.h 2015-11-11 09:52:55 +0000
294+++ src/economy/warehousesupply.h 2016-01-07 18:40:19 +0000
295@@ -55,6 +55,7 @@
296 // Supply implementation
297 PlayerImmovable * get_position(Game &) override;
298 bool is_active() const override;
299+ SupplyProviders provider_type(Game *) const override;
300 bool has_storage() const override;
301 void get_ware_type(WareWorker & type, DescriptionIndex & ware) const override;
302
303
304=== modified file 'src/logic/warehouse.cc'
305--- src/logic/warehouse.cc 2015-11-21 11:34:10 +0000
306+++ src/logic/warehouse.cc 2016-01-07 18:40:19 +0000
307@@ -183,6 +183,7 @@
308
309 /// Warehouse supplies are never active.
310 bool WarehouseSupply::is_active() const {return false;}
311+SupplyProviders WarehouseSupply::provider_type(Game *) const {return SupplyProviders::kWarehouse;}
312
313 bool WarehouseSupply::has_storage() const
314 {

Subscribers

People subscribed via source and target branches

to status/vote changes: