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

Proposed by GunChleoc
Status: Merged
Merged at revision: 9014
Proposed branch: lp:~widelands-dev/widelands/speedup_saveloading
Merge into: lp:widelands
Diff against target: 958 lines (+361/-170)
21 files modified
data/scripting/win_conditions/territorial_functions.lua (+10/-42)
data/scripting/win_conditions/territorial_lord.lua (+5/-3)
data/scripting/win_conditions/territorial_time.lua (+5/-3)
data/scripting/win_conditions/win_condition_functions.lua (+32/-0)
data/scripting/win_conditions/wood_gnome.lua (+13/-39)
src/game_io/game_cmd_queue_packet.cc (+4/-0)
src/logic/game.cc (+5/-1)
src/logic/map.cc (+40/-16)
src/logic/map.h (+26/-5)
src/logic/player.cc (+9/-0)
src/logic/player.h (+5/-0)
src/logic/playercommand.cc (+6/-6)
src/map_io/CMakeLists.txt (+2/-0)
src/map_io/map_saver.cc (+9/-0)
src/map_io/map_wincondition_packet.cc (+75/-0)
src/map_io/map_wincondition_packet.h (+34/-0)
src/map_io/widelands_map_loader.cc (+9/-0)
src/scripting/lua_game.cc (+1/-11)
src/scripting/lua_map.cc (+60/-38)
src/scripting/lua_map.h (+3/-2)
test/maps/lua_testsuite.wmf/scripting/cfield.lua (+8/-4)
To merge this branch: bzr merge lp:~widelands-dev/widelands/speedup_saveloading
Reviewer Review Type Date Requested Status
Toni Förster Approve
Review via email: mp+364205@code.launchpad.net

Commit message

Tweak performance for saveloading and introduce an optional "init" function for winconditions with costly calculations

- Affected win conditions: Artifacts, Wood Gnome, Territorial Time, Territorial Lord.
- Saveload long lists of fields via C++ because Lua persistence is very expensive
- Use map index for iteration when writing
- Clean up writing CmdFlagAction
- Bulk skip all commands with greater duetime when writing command queue packet

Description of the change

We will need either this branch in Build 20 or a separate branch to fix Artifacts, because we broke it.

I'd like this branch for its performance improvements, but it is big, so I can be convinced otherwise.

Some stats for the big map "Magic Mountain" with Territorial Time:

This branch:

 Writing Wincondition Data: 146ms
 Writing Scripting Data: 25ms
 Sum 171ms

 Reading Wincondition Data: 997ms
 Reading Scripting Data: 5ms
 Sum 1 002ms

Trunk:
 Writing Scripting Data: 32 218ms
 Reading Scripting Data: 4 116ms

Writing speed factor: ca. 188x
Reading speed factor: ca. 4x

To post a comment you must log in.
Revision history for this message
Toni Förster (stonerl) wrote :

Tested this, also with old savegames. Works like a charm. Tremendously speeds up the saving and loading (territorial & woodgnome).

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

Continuous integration builds have changed state:

Travis build 4572. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/503990492.
Appveyor build 4359. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_speedup_saveloading-4359.

Revision history for this message
Toni Förster (stonerl) wrote :

Regressiontestsfail, though

review: Needs Fixing (travis)
Revision history for this message
GunChleoc (gunchleoc) wrote :

I forgot to adapt the tests to the new data types. Should be working now.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4579. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/504410525.
Appveyor build 4366. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_speedup_saveloading-4366.

Revision history for this message
Toni Förster (stonerl) :
review: Approve
Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for the review!

@bunnybot merge

Revision history for this message
kaputtnik (franku) wrote :

Here are my values from my old laptop. Seem this branch makes loading slower?

Version bzr9012[speedup_saveloading] (Release)
WidelandsMapLoader::load_map_complete() for 'Magic Mountain' took 5136ms
GameLoader::load() took 11895ms
MapSaver::save() for 'Magic Mountain' took 12376ms
MapSaver::save() for 'Magic Mountain' took 12096ms
GameSaver::save() took 13103ms
WidelandsMapLoader::load_map_complete() for 'Magic Mountain' took 7798ms
GameLoader::load() took 7921ms

Version bzr9008[trunk] (Release)
WidelandsMapLoader::load_map_complete() for 'Magic Mountain' took 5042ms
GameLoader::load() took 12061ms
MapSaver::save() for 'Magic Mountain' took 11165ms
MapSaver::save() for 'Magic Mountain' took 11309ms
GameSaver::save() took 12220ms
WidelandsMapLoader::load_map_complete() for 'Magic Mountain' took 7495ms
GameLoader::load() took 7617ms

Revision history for this message
GunChleoc (gunchleoc) wrote :

The only change that could possibly make it slower are the changes to the map resources packet, so I have reverted that. The important speed up is for the win conditions. All the other changes are omitting data, and the extra boolean comparisons should be a lot faster than writing to / reading from disk.

Have you made more than one test run? The numbers are not that radically different.

Revision history for this message
Toni Förster (stonerl) wrote :

Well I'd say these numbers are within the margin of error. If you compare saving and loading times, you can see the difference this branch makes, though.

@GunChleoc not sure if r9015 was necessary. I don't really see a difference.

Revision history for this message
GunChleoc (gunchleoc) wrote :

It didn't gain us that much anyway, so better safe than sorry this close to the release. The big think here was removing the field lists from Lua ;)

Revision history for this message
Toni Förster (stonerl) wrote :

Sound sane :-) are you planning to add them for b21?

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4580. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/504553847.
Appveyor build 4367. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_speedup_saveloading-4367.

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 4580. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/504553847.

Revision history for this message
Toni Förster (stonerl) wrote :

Travis hiccup while downloading brew bottles.

@bunnybot merge force

Revision history for this message
GunChleoc (gunchleoc) wrote :

I guess we could.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I did some more measurements on my slow machine, and the change in the resources packet didn't matter there. Let's keep it as it is, because the code is more readable.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/scripting/win_conditions/territorial_functions.lua'
2--- data/scripting/win_conditions/territorial_functions.lua 2019-02-21 08:39:13 +0000
3+++ data/scripting/win_conditions/territorial_functions.lua 2019-03-11 07:15:58 +0000
4@@ -7,45 +7,13 @@
5 set_textdomain("win_conditions")
6
7 include "scripting/richtext.lua"
8+include "scripting/win_conditions/win_condition_functions.lua"
9 include "scripting/win_conditions/win_condition_texts.lua"
10
11 local team_str = _"Team %i"
12 local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)."
13 local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)."
14
15--- RST
16--- .. function:: count_owned_fields_for_all_players(fields, players)
17---
18--- Counts all owned fields for each player.
19---
20--- :arg fields: Table of all buildable fields
21--- :arg players: Table of all players
22---
23--- :returns: a table with ``playernumber = count_of_owned_fields`` entries
24---
25-local function count_owned_fields_for_all_players(fields, players)
26- local owned_fields = {}
27- -- init the landsizes for each player
28- for idx,plr in ipairs(players) do
29- owned_fields[plr.number] = 0
30- end
31-
32- for idx,f in ipairs(fields) do
33- -- check if field is owned by a player
34- local owner = f.owner
35- if owner then
36- local owner_number = owner.number
37- if owned_fields[owner_number] == nil then
38- -- In case player was defeated and lost all their warehouses, make sure they don't count
39- owned_fields[owner_number] = -1
40- elseif owned_fields[owner_number] >= 0 then
41- owned_fields[owner_number] = owned_fields[owner_number] + 1
42- end
43- end
44- end
45- return owned_fields
46-end
47-
48 -- Used by calculate_territory_points keep track of when the winner changes
49 local winning_players = {}
50 local winning_teams = {}
51@@ -91,7 +59,7 @@
52 -- First checks if a player was defeated, then fills the ``territory_points`` table
53 -- with current data.
54 --
55--- :arg fields: Table of all buildable fields
56+-- :arg fields: Number of all valuable fields
57 -- :arg players: Table of all players
58 -- :arg wc_descname: String with the win condition's descname
59 -- :arg wc_version: Number with the win condition's descname
60@@ -100,12 +68,12 @@
61 local points = {} -- tracking points of teams and players without teams
62 local territory_was_kept = false
63
64- territory_points.all_player_points = count_owned_fields_for_all_players(fields, players)
65+ territory_points.all_player_points = count_owned_valuable_fields_for_all_players(players)
66 local ranked_players = rank_players(territory_points.all_player_points, players)
67
68 -- Check if we have a winner. The table was sorted, so we can simply grab the first entry.
69 local winning_points = -1
70- if ranked_players[1].points > ( #fields / 2 ) then
71+ if ranked_players[1].points > ( fields / 2 ) then
72 winning_points = ranked_players[1].points
73 end
74
75@@ -161,7 +129,7 @@
76 -- Returns a string containing the current land percentages of players/teams
77 -- for messages to the players
78 --
79--- :arg fields: Table of all buildable fields
80+-- :arg fields: Number of all valuable fields
81 -- :arg has_had: Use "has" for an interim message, "had" for a game over message.
82 --
83 -- :returns: a richtext-formatted string with information on current points for each player/team
84@@ -178,17 +146,17 @@
85 li(
86 (wc_has_territory):bformat(
87 territory_points.points[i][1],
88- _percent(territory_points.points[i][2], #fields),
89+ _percent(territory_points.points[i][2], fields),
90 territory_points.points[i][2],
91- #fields))
92+ fields))
93 else
94 msg = msg ..
95 li(
96 (wc_had_territory):bformat(
97 territory_points.points[i][1],
98- _percent(territory_points.points[i][2], #fields),
99+ _percent(territory_points.points[i][2], fields),
100 territory_points.points[i][2],
101- #fields))
102+ fields))
103 end
104
105 end
106@@ -246,7 +214,7 @@
107 --
108 -- Updates the territory points and sends game over reports
109 --
110--- :arg fields: Table of all buildable fields
111+-- :arg fields: Number of all valuable fields
112 -- :arg players: Table of all players
113 --
114 function territory_game_over(fields, players, wc_descname, wc_version)
115
116=== modified file 'data/scripting/win_conditions/territorial_lord.lua'
117--- data/scripting/win_conditions/territorial_lord.lua 2019-03-02 08:52:08 +0000
118+++ data/scripting/win_conditions/territorial_lord.lua 2019-03-11 07:15:58 +0000
119@@ -23,18 +23,20 @@
120 "that area for at least 20 minutes."
121 )
122
123+local fields = 0
124+
125 return {
126 name = wc_name,
127 description = wc_desc,
128+ init = function()
129+ fields = wl.Game().map:count_conquerable_fields()
130+ end,
131 func = function()
132 local plrs = wl.Game().players
133
134 -- set the objective with the game type for all players
135 broadcast_objective("win_condition", wc_descname, wc_desc)
136
137- -- Table of fields that are worth conquering
138- local fields = wl.Game().map.conquerable_fields
139-
140 -- Configure how long the winner has to hold on to the territory
141 local time_to_keep_territory = 20 * 60 -- 20 minutes
142 -- time in secs, if == 0 -> victory
143
144=== modified file 'data/scripting/win_conditions/territorial_time.lua'
145--- data/scripting/win_conditions/territorial_time.lua 2019-03-02 08:52:08 +0000
146+++ data/scripting/win_conditions/territorial_time.lua 2019-03-11 07:15:58 +0000
147@@ -27,18 +27,20 @@
148 "after 4 hours, whichever comes first."
149 )
150
151+local fields = 0
152+
153 return {
154 name = wc_name,
155 description = wc_desc,
156+ init = function()
157+ fields = wl.Game().map:count_conquerable_fields()
158+ end,
159 func = function()
160 local plrs = wl.Game().players
161
162 -- set the objective with the game type for all players
163 broadcast_objective("win_condition", wc_descname, wc_desc)
164
165- -- Table of fields that are worth conquering
166- local fields = wl.Game().map.conquerable_fields
167-
168 -- variables to track the maximum 4 hours of gametime
169 local remaining_max_time = 4 * 60 * 60 -- 4 hours
170
171
172=== modified file 'data/scripting/win_conditions/win_condition_functions.lua'
173--- data/scripting/win_conditions/win_condition_functions.lua 2019-02-12 17:30:21 +0000
174+++ data/scripting/win_conditions/win_condition_functions.lua 2019-03-11 07:15:58 +0000
175@@ -147,6 +147,38 @@
176
177
178 -- RST
179+-- .. function:: count_owned_valuable_fields_for_all_players(players[, attribute])
180+--
181+-- Counts all owned fields for each player.
182+--
183+-- :arg players: Table of all players
184+-- :arg attribute: If this is set, only count fields that have an immovable with this attribute
185+--
186+-- :returns: a table with ``playernumber = count_of_owned_fields`` entries
187+--
188+function count_owned_valuable_fields_for_all_players(players, attribute)
189+ attribute = attribute or ""
190+
191+ local owned_fields = {}
192+
193+ -- Get number of currently owned valuable fields per player.
194+ -- This table can contain defeated players.
195+ local all_plrpoints = wl.Game().map:count_owned_valuable_fields(attribute)
196+
197+ -- Insert points for all players who are still in the game, and 0 points for defeated players.
198+ for idx,plr in ipairs(players) do
199+ if (plr.defeated) then
200+ owned_fields[plr.number] = 0
201+ else
202+ owned_fields[plr.number] = all_plrpoints[plr.number]
203+ end
204+ end
205+ return owned_fields
206+end
207+
208+
209+
210+-- RST
211 -- .. function:: rank_players(all_player_points, plrs)
212 --
213 -- Rank the players and teams according to the highest points
214
215=== modified file 'data/scripting/win_conditions/wood_gnome.lua'
216--- data/scripting/win_conditions/wood_gnome.lua 2019-03-02 08:52:08 +0000
217+++ data/scripting/win_conditions/wood_gnome.lua 2019-03-11 07:15:58 +0000
218@@ -24,6 +24,10 @@
219 return {
220 name = wc_name,
221 description = wc_desc,
222+ init = function()
223+ -- Calculate valuable fields
224+ wl.Game().map:count_terrestrial_fields()
225+ end,
226 func = function()
227 local plrs = wl.Game().players
228 local game = wl.Game()
229@@ -34,51 +38,21 @@
230 -- set the objective with the game type for all players
231 broadcast_objective("win_condition", wc_descname, wc_desc)
232
233- -- Table of terrestrial fields
234- local fields = wl.Game().map.terrestrial_fields
235-
236 -- The function to calculate the current points.
237 local _last_time_calculated = -100000
238- local _plrpoints = {}
239+ local playerpoints = {}
240 local function _calc_points()
241- local game = wl.Game()
242
243 if _last_time_calculated > game.time - 5000 then
244- return _plrpoints
245- end
246-
247- -- clear out the table. We count afresh.
248- for k,v in pairs(_plrpoints) do
249- _plrpoints[k] = 0
250- end
251-
252- -- Insert all players who are still in the game.
253- for idx,plr in ipairs(plrs) do
254- _plrpoints[plr.number] = 0
255- end
256-
257- for idf,f in ipairs(fields) do
258- -- check if field is owned by a player
259- local owner = f.owner
260- if owner and not owner.defeated then
261- owner = owner.number
262- -- check if field has an immovable
263- local imm = f.immovable
264- if imm then
265- -- check if immovable is a tree
266- if imm:has_attribute("tree") then
267- _plrpoints[owner] = _plrpoints[owner] + 1
268- end
269- end
270- end
271- end
272-
273+ return
274+ end
275+
276+ playerpoints = count_owned_valuable_fields_for_all_players(plrs, "tree")
277 _last_time_calculated = game.time
278- return _plrpoints
279 end
280
281 local function _send_state(remaining_time, plrs, show_popup)
282- local playerpoints = _calc_points()
283+ _calc_points()
284 local msg = format_remaining_time(remaining_time) .. vspace(8) .. game_status.body
285
286 for idx,plr in ipairs(plrs) do
287@@ -92,7 +66,7 @@
288 end
289
290 local function _game_over(plrs)
291- local playerpoints = _calc_points()
292+ _calc_points()
293 local points = {}
294 for idx,plr in ipairs(plrs) do
295 points[#points + 1] = { plr, playerpoints[plr.number] }
296@@ -131,8 +105,8 @@
297 name = wc_trees_owned,
298 pic = "images/wui/stats/genstats_trees.png",
299 calculator = function(p)
300- local pts = _calc_points(p)
301- return pts[p.number]
302+ _calc_points(p)
303+ return playerpoints[p.number] or 0
304 end,
305 }
306
307
308=== modified file 'src/game_io/game_cmd_queue_packet.cc'
309--- src/game_io/game_cmd_queue_packet.cc 2019-02-23 11:00:49 +0000
310+++ src/game_io/game_cmd_queue_packet.cc 2019-03-11 07:15:58 +0000
311@@ -98,6 +98,10 @@
312
313 while (!p.empty()) {
314 const CmdQueue::CmdItem& it = p.top();
315+ if (it.cmd->duetime() > time) {
316+ // Time is the primary sorting key, so we can't have any additional commands in this queue for this time
317+ break;
318+ }
319 if (it.cmd->duetime() == time) {
320 if (upcast(GameLogicCommand, cmd, it.cmd)) {
321 // The id (aka command type)
322
323=== modified file 'src/logic/game.cc'
324--- src/logic/game.cc 2019-03-02 10:34:39 +0000
325+++ src/logic/game.cc 2019-03-11 07:15:58 +0000
326@@ -314,10 +314,14 @@
327
328 // Check for win_conditions
329 if (!settings.scenario) {
330+ loader_ui->step(_("Initializing game…"));
331 std::unique_ptr<LuaTable> table(lua().run_script(settings.win_condition_script));
332- get_ibase()->log_message(_("Initializing game…"));
333 table->do_not_warn_about_unaccessed_keys();
334 win_condition_displayname_ = table->get_string("name");
335+ if (table->has_key<std::string>("init")) {
336+ std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("init");
337+ cr->resume();
338+ }
339 std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("func");
340 enqueue_command(new CmdLuaCoroutine(get_gametime() + 100, std::move(cr)));
341 } else {
342
343=== modified file 'src/logic/map.cc'
344--- src/logic/map.cc 2019-03-02 09:31:11 +0000
345+++ src/logic/map.cc 2019-03-11 07:15:58 +0000
346@@ -257,8 +257,11 @@
347 }
348 }
349
350-std::set<FCoords> Map::calculate_all_conquerable_fields() const {
351- std::set<FCoords> result;
352+size_t Map::count_all_conquerable_fields() {
353+ if (!valuable_fields_.empty()) {
354+ // Already calculated
355+ return valuable_fields_.size();
356+ }
357
358 std::set<FCoords> coords_to_check;
359
360@@ -267,17 +270,17 @@
361
362 // If we don't have the given coordinates yet, walk the map and collect conquerable fields,
363 // initialized with the given radius around the coordinates
364- const auto walk_starting_coords = [this, &result, &coords_to_check](
365+ const auto walk_starting_coords = [this, &coords_to_check](
366 const Coords& coords, int radius) {
367 FCoords fcoords = get_fcoords(coords);
368
369 // We already have these coordinates
370- if (result.count(fcoords) == 1) {
371+ if (valuable_fields_.count(fcoords) == 1) {
372 return;
373 }
374
375 // Add starting field
376- result.insert(fcoords);
377+ valuable_fields_.insert(fcoords);
378
379 // Add outer land coordinates around the starting field for the given radius
380 std::unique_ptr<Widelands::HollowArea<>> hollow_area(
381@@ -317,8 +320,8 @@
382
383 // We do the caps check first, because the comparison is faster than the container
384 // check
385- if ((fcoords.field->maxcaps() & MOVECAPS_WALK) && (result.count(fcoords) == 0)) {
386- result.insert(fcoords);
387+ if ((fcoords.field->maxcaps() & MOVECAPS_WALK) && (valuable_fields_.count(fcoords) == 0)) {
388+ valuable_fields_.insert(fcoords);
389 coords_to_check.insert(fcoords);
390 }
391 } while (map_region->advance(*this));
392@@ -342,23 +345,44 @@
393 }
394 }
395
396- log("%" PRIuS " found ... ", result.size());
397- return result;
398+ log("%" PRIuS " found ... ", valuable_fields_.size());
399+ return valuable_fields_.size();
400 }
401
402-std::set<FCoords> Map::calculate_all_fields_excluding_caps(NodeCaps caps) const {
403+size_t Map::count_all_fields_excluding_caps(NodeCaps caps) {
404+ if (!valuable_fields_.empty()) {
405+ // Already calculated
406+ return valuable_fields_.size();
407+ }
408+
409 log("Collecting valuable fields ... ");
410 ScopedTimer timer("took %ums");
411
412- std::set<FCoords> result;
413 for (MapIndex i = 0; i < max_index(); ++i) {
414 Field& field = fields_[i];
415 if (!(field.nodecaps() & caps)) {
416- result.insert(get_fcoords(field));
417- }
418- }
419-
420- log("%" PRIuS " found ... ", result.size());
421+ valuable_fields_.insert(get_fcoords(field));
422+ }
423+ }
424+
425+ log("%" PRIuS " found ... ", valuable_fields_.size());
426+ return valuable_fields_.size();
427+}
428+
429+std::map<PlayerNumber, size_t> Map::count_owned_valuable_fields(const std::string& immovable_attribute) const {
430+ std::map<PlayerNumber, size_t> result;
431+ const bool use_attribute = !immovable_attribute.empty();
432+ const uint32_t attribute_id = use_attribute ? MapObjectDescr::get_attribute_id(immovable_attribute) : 0U;
433+ for (const FCoords& fcoords : valuable_fields_) {
434+ if (use_attribute) {
435+ const BaseImmovable* imm = fcoords.field->get_immovable();
436+ if (imm != nullptr && imm->has_attribute(attribute_id)) {
437+ result[fcoords.field->get_owned_by()] += 1;
438+ }
439+ } else {
440+ result[fcoords.field->get_owned_by()] += 1;
441+ }
442+ }
443 return result;
444 }
445
446
447=== modified file 'src/logic/map.h'
448--- src/logic/map.h 2019-02-26 05:56:01 +0000
449+++ src/logic/map.h 2019-03-11 07:15:58 +0000
450@@ -165,11 +165,25 @@
451 void recalc_whole_map(const World& world);
452 void recalc_for_field_area(const World& world, Area<FCoords>);
453
454- /// Calculates and returns a list of the fields that could be conquered by a player throughout a
455- /// game. Useful for territorial win conditions.
456- std::set<FCoords> calculate_all_conquerable_fields() const;
457- /// Calculates and returns a list of the fields that do not have the given caps.
458- std::set<FCoords> calculate_all_fields_excluding_caps(NodeCaps caps) const;
459+ /**
460+ * If the valuable fields are empty, calculates all fields that could be conquered by a player throughout a game.
461+ * Useful for territorial win conditions.
462+ * Returns the amount of valuable fields.
463+ */
464+ size_t count_all_conquerable_fields();
465+
466+ /**
467+ * If the valuable fields are empty, calculates which fields do not have the given caps and adds the to the list of valuable fields.
468+ * Useful for win conditions.
469+ * Returns the amount of valuable fields.
470+ */
471+ size_t count_all_fields_excluding_caps(NodeCaps caps);
472+
473+ /**
474+ * Counts the valuable fields that are owned by each player. Only players that currently own a field are added.
475+ * Returns a map of <player number, number of owned fields>.
476+ */
477+ std::map<PlayerNumber, size_t> count_owned_valuable_fields(const std::string& immovable_attribute) const;
478
479 /***
480 * Ensures that resources match their adjacent terrains.
481@@ -438,6 +452,10 @@
482 return &objectives_;
483 }
484
485+ std::set<FCoords>* mutable_valuable_fields() {
486+ return &valuable_fields_;
487+ }
488+
489 /// Returns the military influence on a location from an area.
490 MilitaryInfluence calc_influence(Coords, Area<>) const;
491
492@@ -535,6 +553,9 @@
493
494 Objectives objectives_;
495
496+ // Fields that are important for the player to own in a win condition
497+ std::set<FCoords> valuable_fields_;
498+
499 MapVersion map_version_;
500 };
501
502
503=== modified file 'src/logic/player.cc'
504--- src/logic/player.cc 2019-02-23 11:00:49 +0000
505+++ src/logic/player.cc 2019-03-11 07:15:58 +0000
506@@ -253,6 +253,15 @@
507 return &other != this && (!team_number_ || team_number_ != other.team_number_);
508 }
509
510+bool Player::is_defeated() const {
511+ for (const auto& economy : economies()) {
512+ if (!economy.second->warehouses().empty()) {
513+ return false;
514+ }
515+ }
516+ return true;
517+}
518+
519 void Player::AiPersistentState::initialize() {
520 colony_scan_area = kColonyScanStartArea;
521 trees_around_cutters = 0;
522
523=== modified file 'src/logic/player.h'
524--- src/logic/player.h 2019-02-23 11:00:49 +0000
525+++ src/logic/player.h 2019-03-11 07:15:58 +0000
526@@ -148,6 +148,11 @@
527
528 bool is_hostile(const Player&) const;
529
530+ /**
531+ * Returns whether the player lost the last warehouse.
532+ */
533+ bool is_defeated() const;
534+
535 // For cheating
536 void set_see_all(bool const t) {
537 see_all_ = t;
538
539=== modified file 'src/logic/playercommand.cc'
540--- src/logic/playercommand.cc 2019-02-23 11:00:49 +0000
541+++ src/logic/playercommand.cc 2019-03-11 07:15:58 +0000
542@@ -450,14 +450,17 @@
543 ser.unsigned_32(serial);
544 }
545
546-constexpr uint16_t kCurrentPacketVersionCmdFlagAction = 1;
547+constexpr uint16_t kCurrentPacketVersionCmdFlagAction = 2;
548
549 void CmdFlagAction::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) {
550 try {
551 const uint16_t packet_version = fr.unsigned_16();
552- if (packet_version == kCurrentPacketVersionCmdFlagAction) {
553+ // TODO(GunChleoc): Savegame compatibility, remove after Build 21
554+ if (packet_version >= 1 && packet_version <= kCurrentPacketVersionCmdFlagAction) {
555 PlayerCommand::read(fr, egbase, mol);
556- fr.unsigned_8();
557+ if (packet_version == 1) {
558+ fr.unsigned_8();
559+ }
560 serial = get_object_serial_or_zero<Flag>(fr.unsigned_32(), mol);
561 } else {
562 throw UnhandledVersionError(
563@@ -472,9 +475,6 @@
564 fw.unsigned_16(kCurrentPacketVersionCmdFlagAction);
565 // Write base classes
566 PlayerCommand::write(fw, egbase, mos);
567- // Now action
568- fw.unsigned_8(0);
569-
570 // Now serial
571 fw.unsigned_32(mos.get_object_file_index_or_zero(egbase.objects().get_object(serial)));
572 }
573
574=== modified file 'src/map_io/CMakeLists.txt'
575--- src/map_io/CMakeLists.txt 2017-11-20 13:50:51 +0000
576+++ src/map_io/CMakeLists.txt 2019-03-11 07:15:58 +0000
577@@ -87,6 +87,8 @@
578 map_terrain_packet.h
579 map_version_packet.cc
580 map_version_packet.h
581+ map_wincondition_packet.cc
582+ map_wincondition_packet.h
583 DEPENDS
584 base_exceptions
585 base_log
586
587=== modified file 'src/map_io/map_saver.cc'
588--- src/map_io/map_saver.cc 2019-02-23 11:00:49 +0000
589+++ src/map_io/map_saver.cc 2019-03-11 07:15:58 +0000
590@@ -58,6 +58,7 @@
591 #include "map_io/map_scripting_packet.h"
592 #include "map_io/map_terrain_packet.h"
593 #include "map_io/map_version_packet.h"
594+#include "map_io/map_wincondition_packet.h"
595
596 namespace Widelands {
597
598@@ -220,6 +221,14 @@
599 log("took %ums\n ", timer.ms_since_last_query());
600
601 if (is_game) {
602+ // Map data used by win conditions
603+ log("Writing Wincondition Data ... ");
604+ {
605+ MapWinconditionPacket p;
606+ p.write(fs_, *egbase_.mutable_map(), *mos_);
607+ }
608+ log("took %ums\n ", timer.ms_since_last_query());
609+
610 // DATA PACKETS
611 if (mos_->get_nr_flags()) {
612 log("Writing Flagdata Data ... ");
613
614=== added file 'src/map_io/map_wincondition_packet.cc'
615--- src/map_io/map_wincondition_packet.cc 1970-01-01 00:00:00 +0000
616+++ src/map_io/map_wincondition_packet.cc 2019-03-11 07:15:58 +0000
617@@ -0,0 +1,75 @@
618+/*
619+ * Copyright (C) 2019 by the Widelands Development Team
620+ *
621+ * This program is free software; you can redistribute it and/or
622+ * modify it under the terms of the GNU General Public License
623+ * as published by the Free Software Foundation; either version 2
624+ * of the License, or (at your option) any later version.
625+ *
626+ * This program is distributed in the hope that it will be useful,
627+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
628+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
629+ * GNU General Public License for more details.
630+ *
631+ * You should have received a copy of the GNU General Public License
632+ * along with this program; if not, write to the Free Software
633+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
634+ *
635+ */
636+
637+#include "map_io/map_wincondition_packet.h"
638+
639+#include "io/fileread.h"
640+#include "io/filewrite.h"
641+#include "logic/game_data_error.h"
642+
643+namespace Widelands {
644+
645+constexpr uint8_t kCurrentPacketVersion = 1;
646+
647+void MapWinconditionPacket::read(FileSystem& fs, Map& map, MapObjectLoader&) {
648+ if (!fs.file_exists("binary/wincondition")) {
649+ // This can be empty when win conditions don't need any special map data
650+ return;
651+ }
652+
653+ try {
654+ FileRead fr;
655+ fr.open(fs, "binary/wincondition");
656+
657+ const uint8_t packet_version = fr.unsigned_8();
658+ if (packet_version == kCurrentPacketVersion) {
659+ const size_t no_of_fields = fr.unsigned_32();
660+ std::set<FCoords>* fields = map.mutable_valuable_fields();
661+ fields->clear();
662+
663+ for (size_t i = 0; i < no_of_fields; ++i) {
664+ const int32_t x = fr.signed_16();
665+ const int32_t y = fr.signed_16();
666+ fields->insert(map.get_fcoords(Coords(x, y)));
667+ }
668+ } else {
669+ throw UnhandledVersionError("MapWinconditionPacket", packet_version, kCurrentPacketVersion);
670+ }
671+ } catch (const WException& e) {
672+ throw GameDataError("win condition data: %s", e.what());
673+ }
674+}
675+
676+void MapWinconditionPacket::write(FileSystem& fs, Map& map, MapObjectSaver&) {
677+ // We only write this packet if we have something interesting to write to it.
678+ std::set<FCoords>& fields = *map.mutable_valuable_fields();
679+ if (!fields.empty()) {
680+ FileWrite fw;
681+ fw.unsigned_8(kCurrentPacketVersion);
682+
683+ fw.unsigned_32(fields.size());
684+ for (const auto& field : fields) {
685+ fw.signed_16(field.x);
686+ fw.signed_16(field.y);
687+ }
688+
689+ fw.write(fs, "binary/wincondition");
690+ }
691+}
692+} // namespace Widelands
693
694=== added file 'src/map_io/map_wincondition_packet.h'
695--- src/map_io/map_wincondition_packet.h 1970-01-01 00:00:00 +0000
696+++ src/map_io/map_wincondition_packet.h 2019-03-11 07:15:58 +0000
697@@ -0,0 +1,34 @@
698+/*
699+ * Copyright (C) 2019 by the Widelands Development Team
700+ *
701+ * This program is free software; you can redistribute it and/or
702+ * modify it under the terms of the GNU General Public License
703+ * as published by the Free Software Foundation; either version 2
704+ * of the License, or (at your option) any later version.
705+ *
706+ * This program is distributed in the hope that it will be useful,
707+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
708+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
709+ * GNU General Public License for more details.
710+ *
711+ * You should have received a copy of the GNU General Public License
712+ * along with this program; if not, write to the Free Software
713+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
714+ *
715+ */
716+
717+#ifndef WL_MAP_IO_MAP_WINCONDITION_PACKET_H
718+#define WL_MAP_IO_MAP_WINCONDITION_PACKET_H
719+
720+#include "logic/map.h"
721+
722+namespace Widelands {
723+
724+/// The anything needed by win conditions
725+struct MapWinconditionPacket {
726+ void read(FileSystem&, Map& map, MapObjectLoader&);
727+ void write(FileSystem&, Map& map, MapObjectSaver&);
728+};
729+} // namespace Widelands
730+
731+#endif // end of include guard: WL_MAP_IO_MAP_WINCONDITION_PACKET_H
732
733=== modified file 'src/map_io/widelands_map_loader.cc'
734--- src/map_io/widelands_map_loader.cc 2019-02-23 11:00:49 +0000
735+++ src/map_io/widelands_map_loader.cc 2019-03-11 07:15:58 +0000
736@@ -55,6 +55,7 @@
737 #include "map_io/map_scripting_packet.h"
738 #include "map_io/map_terrain_packet.h"
739 #include "map_io/map_version_packet.h"
740+#include "map_io/map_wincondition_packet.h"
741 #include "map_io/tribes_legacy_lookup_table.h"
742 #include "map_io/world_legacy_lookup_table.h"
743
744@@ -310,6 +311,14 @@
745 }
746 log("took %ums\n ", timer.ms_since_last_query());
747
748+ // Map data used by win conditions.
749+ log("Reading Wincondition Data ... ");
750+ {
751+ MapWinconditionPacket p;
752+ p.read(*fs_, *egbase.mutable_map(), *mol_);
753+ }
754+ log("took %ums\n ", timer.ms_since_last_query());
755+
756 // Objectives. They are not needed in the Editor, since they are fully
757 // defined through Lua scripting. They are also not required for a game,
758 // since they will be only be set after it has started.
759
760=== modified file 'src/scripting/lua_game.cc'
761--- src/scripting/lua_game.cc 2019-02-23 11:00:49 +0000
762+++ src/scripting/lua_game.cc 2019-03-11 07:15:58 +0000
763@@ -173,17 +173,7 @@
764 (RO) :const:`true` if this player was defeated, :const:`false` otherwise
765 */
766 int LuaPlayer::get_defeated(lua_State* L) {
767- Player& p = get(L, get_egbase(L));
768- bool is_defeated = true;
769-
770- for (const auto& economy : p.economies()) {
771- if (!economy.second->warehouses().empty()) {
772- is_defeated = false;
773- break;
774- }
775- }
776-
777- lua_pushboolean(L, is_defeated);
778+ lua_pushboolean(L, get(L, get_egbase(L)).is_defeated());
779 return 1;
780 }
781
782
783=== modified file 'src/scripting/lua_map.cc'
784--- src/scripting/lua_map.cc 2019-02-26 12:02:52 +0000
785+++ src/scripting/lua_map.cc 2019-03-11 07:15:58 +0000
786@@ -1186,9 +1186,11 @@
787 */
788 const char LuaMap::className[] = "Map";
789 const MethodType<LuaMap> LuaMap::Methods[] = {
790+ METHOD(LuaMap, count_conquerable_fields), METHOD(LuaMap, count_terrestrial_fields),
791+ METHOD(LuaMap, count_owned_valuable_fields),
792 METHOD(LuaMap, place_immovable), METHOD(LuaMap, get_field),
793 METHOD(LuaMap, recalculate), METHOD(LuaMap, recalculate_seafaring),
794- METHOD(LuaMap, set_port_space), {nullptr, nullptr},
795+ METHOD(LuaMap, set_port_space), {nullptr, nullptr},
796 };
797 const PropertyType<LuaMap> LuaMap::Properties[] = {
798 PROP_RO(LuaMap, allows_seafaring),
799@@ -1197,8 +1199,6 @@
800 PROP_RO(LuaMap, width),
801 PROP_RO(LuaMap, height),
802 PROP_RO(LuaMap, player_slots),
803- PROP_RO(LuaMap, conquerable_fields),
804- PROP_RO(LuaMap, terrestrial_fields),
805 {nullptr, nullptr, nullptr},
806 };
807
808@@ -1299,47 +1299,69 @@
809 return 1;
810 }
811
812-/* RST
813- .. attribute:: conquerable_fields
814-
815- (RO) Calculates and returns all reachable fields that a player could build on.
816-
817- **Note:** This function is expensive, so call it seldom.
818-*/
819-int LuaMap::get_conquerable_fields(lua_State* L) {
820- lua_newtable(L);
821- int counter = 0;
822- for (const Widelands::FCoords& fcoords :
823- get_egbase(L).map().calculate_all_conquerable_fields()) {
824- lua_pushinteger(L, ++counter);
825- to_lua<LuaMaps::LuaField>(L, new LuaMaps::LuaField(fcoords));
826- lua_settable(L, -3);
827- }
828- return 1;
829-}
830-
831-/* RST
832- .. attribute:: terrestrial_fields
833-
834- (RO) Calculates and returns all fields that are not swimmable.
835-*/
836-int LuaMap::get_terrestrial_fields(lua_State* L) {
837- lua_newtable(L);
838- int counter = 0;
839- for (const Widelands::FCoords& fcoords :
840- get_egbase(L).map().calculate_all_fields_excluding_caps(MOVECAPS_SWIM)) {
841- lua_pushinteger(L, ++counter);
842- to_lua<LuaMaps::LuaField>(L, new LuaMaps::LuaField(fcoords));
843- lua_settable(L, -3);
844- }
845- return 1;
846-}
847
848 /*
849 ==========================================================
850 LUA METHODS
851 ==========================================================
852 */
853+
854+/* RST
855+ .. method:: count_conquerable_fields()
856+
857+ (RO) Counts all reachable fields that a player could build on.
858+
859+ **Note:** The fields are only calculated afresh when this is called for the first time.
860+
861+ :returns: An integer with the amount of fields.
862+*/
863+int LuaMap::count_conquerable_fields(lua_State* L) {
864+ lua_pushinteger(L, get_egbase(L).mutable_map()->count_all_conquerable_fields());
865+ return 1;
866+}
867+
868+/* RST
869+ .. method:: count_terrestrial_fields()
870+
871+ (RO) Counts all fields that are not swimmable.
872+
873+ **Note:** The fields are only calculated afresh when this is called for the first time.
874+
875+ :returns: An integer with the amount of fields.
876+*/
877+int LuaMap::count_terrestrial_fields(lua_State* L) {
878+ lua_pushinteger(L, get_egbase(L).mutable_map()->count_all_fields_excluding_caps(MOVECAPS_SWIM));
879+ return 1;
880+}
881+
882+
883+/* RST
884+ .. method:: count_owned_valuable_fields([immovable_attribute])
885+
886+ (RO) Counts the number of owned valuable fields for all players.
887+
888+ :arg name: *Optional*. If this is set, only count fields that have an
889+ immovable with the given atttribute.
890+ :type name: :class:`string`
891+
892+ :returns: A table mapping player numbers to their number of owned fields.
893+*/
894+int LuaMap::count_owned_valuable_fields(lua_State* L) {
895+ if (lua_gettop(L) > 2) {
896+ report_error(L, "Does not take more than one argument.");
897+ }
898+ const std::string attribute = lua_gettop(L) == 2 ? luaL_checkstring(L, -1) : "";
899+
900+ lua_newtable(L);
901+ for (const auto& fieldinfo :
902+ get_egbase(L).map().count_owned_valuable_fields(attribute)) {
903+ lua_pushinteger(L, fieldinfo.first);
904+ lua_pushinteger(L, fieldinfo.second);
905+ lua_settable(L, -3);
906+ }
907+ return 1;
908+}
909+
910 /* RST
911 .. method:: place_immovable(name, field, from_where)
912
913
914=== modified file 'src/scripting/lua_map.h'
915--- src/scripting/lua_map.h 2019-02-24 22:50:04 +0000
916+++ src/scripting/lua_map.h 2019-03-11 07:15:58 +0000
917@@ -92,12 +92,13 @@
918 int get_width(lua_State*);
919 int get_height(lua_State*);
920 int get_player_slots(lua_State*);
921- int get_conquerable_fields(lua_State*);
922- int get_terrestrial_fields(lua_State*);
923
924 /*
925 * Lua methods
926 */
927+ int count_conquerable_fields(lua_State*);
928+ int count_terrestrial_fields(lua_State*);
929+ int count_owned_valuable_fields(lua_State*);
930 int place_immovable(lua_State*);
931 int get_field(lua_State*);
932 int recalculate(lua_State*);
933
934=== modified file 'test/maps/lua_testsuite.wmf/scripting/cfield.lua'
935--- test/maps/lua_testsuite.wmf/scripting/cfield.lua 2019-02-23 09:58:39 +0000
936+++ test/maps/lua_testsuite.wmf/scripting/cfield.lua 2019-03-11 07:15:58 +0000
937@@ -330,13 +330,17 @@
938 end
939
940 function field_caps_tests:test_conquerable_fields_does_not_crash()
941- local fields = map.conquerable_fields
942- assert_equal(false, fields[1] == nil)
943+ assert_equal(5028, map:count_conquerable_fields())
944+
945+ local owned_fields = map:count_owned_valuable_fields()
946+ assert_equal(404, owned_fields[1])
947 end
948
949 function field_caps_tests:test_terrestrial_fields_does_not_crash()
950- local fields = map.terrestrial_fields
951- assert_equal(false, fields[1] == nil)
952+ assert_equal(5028, map:count_terrestrial_fields())
953+
954+ local owned_fields = map:count_owned_valuable_fields("tree")
955+ assert_equal(nil, owned_fields[1])
956 end
957
958 -- ===============

Subscribers

People subscribed via source and target branches

to status/vote changes: