widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #16216
[Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
GunChleoc has proposed merging lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands.
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
- Write map resources packet as string
- Clean up writing CmdFlagAction
- Bulk skip all commands with greater duetime when writing command queue packet
Requested reviews:
Widelands Developers (widelands-dev)
Related bugs:
Bug #1818402 in widelands: "Move territorial calculations to the init screen"
https://bugs.launchpad.net/widelands/+bug/1818402
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/speedup_saveloading/+merge/364205
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
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands.
=== modified file 'data/scripting/win_conditions/territorial_functions.lua'
--- data/scripting/win_conditions/territorial_functions.lua 2019-02-21 08:39:13 +0000
+++ data/scripting/win_conditions/territorial_functions.lua 2019-03-09 11:05:59 +0000
@@ -7,45 +7,13 @@
set_textdomain("win_conditions")
include "scripting/richtext.lua"
+include "scripting/win_conditions/win_condition_functions.lua"
include "scripting/win_conditions/win_condition_texts.lua"
local team_str = _"Team %i"
local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)."
local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)."
--- RST
--- .. function:: count_owned_fields_for_all_players(fields, players)
---
--- Counts all owned fields for each player.
---
--- :arg fields: Table of all buildable fields
--- :arg players: Table of all players
---
--- :returns: a table with ``playernumber = count_of_owned_fields`` entries
---
-local function count_owned_fields_for_all_players(fields, players)
- local owned_fields = {}
- -- init the landsizes for each player
- for idx,plr in ipairs(players) do
- owned_fields[plr.number] = 0
- end
-
- for idx,f in ipairs(fields) do
- -- check if field is owned by a player
- local owner = f.owner
- if owner then
- local owner_number = owner.number
- if owned_fields[owner_number] == nil then
- -- In case player was defeated and lost all their warehouses, make sure they don't count
- owned_fields[owner_number] = -1
- elseif owned_fields[owner_number] >= 0 then
- owned_fields[owner_number] = owned_fields[owner_number] + 1
- end
- end
- end
- return owned_fields
-end
-
-- Used by calculate_territory_points keep track of when the winner changes
local winning_players = {}
local winning_teams = {}
@@ -91,7 +59,7 @@
-- First checks if a player was defeated, then fills the ``territory_points`` table
-- with current data.
--
--- :arg fields: Table of all buildable fields
+-- :arg fields: Number of all valuable fields
-- :arg players: Table of all players
-- :arg wc_descname: String with the win condition's descname
-- :arg wc_version: Number with the win condition's descname
@@ -100,12 +68,12 @@
local points = {} -- tracking points of teams and players without teams
local territory_was_kept = false
- territory_points.all_player_points = count_owned_fields_for_all_players(fields, players)
+ territory_points.all_player_points = count_owned_valuable_fields_for_all_players(players)
local ranked_players = rank_players(territory_points.all_player_points, players)
-- Check if we have a winner. The table was sorted, so we can simply grab the first entry.
local winning_points = -1
- if ranked_players[1].points > ( #fields / 2 ) then
+ if ranked_players[1].points > ( fields / 2 ) then
winning_points = ranked_players[1].points
end
@@ -161,7 +129,7 @@
-- Returns a string containing the current land percentages of players/teams
-- for messages to the players
--
--- :arg fields: Table of all buildable fields
+-- :arg fields: Number of all valuable fields
-- :arg has_had: Use "has" for an interim message, "had" for a game over message.
--
-- :returns: a richtext-formatted string with information on current points for each player/team
@@ -178,17 +146,17 @@
li(
(wc_has_territory):bformat(
territory_points.points[i][1],
- _percent(territory_points.points[i][2], #fields),
+ _percent(territory_points.points[i][2], fields),
territory_points.points[i][2],
- #fields))
+ fields))
else
msg = msg ..
li(
(wc_had_territory):bformat(
territory_points.points[i][1],
- _percent(territory_points.points[i][2], #fields),
+ _percent(territory_points.points[i][2], fields),
territory_points.points[i][2],
- #fields))
+ fields))
end
end
@@ -246,7 +214,7 @@
--
-- Updates the territory points and sends game over reports
--
--- :arg fields: Table of all buildable fields
+-- :arg fields: Number of all valuable fields
-- :arg players: Table of all players
--
function territory_game_over(fields, players, wc_descname, wc_version)
=== modified file 'data/scripting/win_conditions/territorial_lord.lua'
--- data/scripting/win_conditions/territorial_lord.lua 2019-03-02 08:52:08 +0000
+++ data/scripting/win_conditions/territorial_lord.lua 2019-03-09 11:05:59 +0000
@@ -23,18 +23,20 @@
"that area for at least 20 minutes."
)
+local fields = 0
+
return {
name = wc_name,
description = wc_desc,
+ init = function()
+ fields = wl.Game().map:count_conquerable_fields()
+ end,
func = function()
local plrs = wl.Game().players
-- set the objective with the game type for all players
broadcast_objective("win_condition", wc_descname, wc_desc)
- -- Table of fields that are worth conquering
- local fields = wl.Game().map.conquerable_fields
-
-- Configure how long the winner has to hold on to the territory
local time_to_keep_territory = 20 * 60 -- 20 minutes
-- time in secs, if == 0 -> victory
=== modified file 'data/scripting/win_conditions/territorial_time.lua'
--- data/scripting/win_conditions/territorial_time.lua 2019-03-02 08:52:08 +0000
+++ data/scripting/win_conditions/territorial_time.lua 2019-03-09 11:05:59 +0000
@@ -27,18 +27,20 @@
"after 4 hours, whichever comes first."
)
+local fields = 0
+
return {
name = wc_name,
description = wc_desc,
+ init = function()
+ fields = wl.Game().map:count_conquerable_fields()
+ end,
func = function()
local plrs = wl.Game().players
-- set the objective with the game type for all players
broadcast_objective("win_condition", wc_descname, wc_desc)
- -- Table of fields that are worth conquering
- local fields = wl.Game().map.conquerable_fields
-
-- variables to track the maximum 4 hours of gametime
local remaining_max_time = 4 * 60 * 60 -- 4 hours
=== modified file 'data/scripting/win_conditions/win_condition_functions.lua'
--- data/scripting/win_conditions/win_condition_functions.lua 2019-02-12 17:30:21 +0000
+++ data/scripting/win_conditions/win_condition_functions.lua 2019-03-09 11:05:59 +0000
@@ -147,6 +147,38 @@
-- RST
+-- .. function:: count_owned_valuable_fields_for_all_players(players[, attribute])
+--
+-- Counts all owned fields for each player.
+--
+-- :arg players: Table of all players
+-- :arg attribute: If this is set, only count fields that have an immovable with this attribute
+--
+-- :returns: a table with ``playernumber = count_of_owned_fields`` entries
+--
+function count_owned_valuable_fields_for_all_players(players, attribute)
+ attribute = attribute or ""
+
+ local owned_fields = {}
+
+ -- Get number of currently owned valuable fields per player.
+ -- This table can contain defeated players.
+ local all_plrpoints = wl.Game().map:count_owned_valuable_fields(attribute)
+
+ -- Insert points for all players who are still in the game, and 0 points for defeated players.
+ for idx,plr in ipairs(players) do
+ if (plr.defeated) then
+ owned_fields[plr.number] = 0
+ else
+ owned_fields[plr.number] = all_plrpoints[plr.number]
+ end
+ end
+ return owned_fields
+end
+
+
+
+-- RST
-- .. function:: rank_players(all_player_points, plrs)
--
-- Rank the players and teams according to the highest points
=== modified file 'data/scripting/win_conditions/wood_gnome.lua'
--- data/scripting/win_conditions/wood_gnome.lua 2019-03-02 08:52:08 +0000
+++ data/scripting/win_conditions/wood_gnome.lua 2019-03-09 11:05:59 +0000
@@ -24,6 +24,10 @@
return {
name = wc_name,
description = wc_desc,
+ init = function()
+ -- Calculate valuable fields
+ wl.Game().map:count_terrestrial_fields()
+ end,
func = function()
local plrs = wl.Game().players
local game = wl.Game()
@@ -34,51 +38,21 @@
-- set the objective with the game type for all players
broadcast_objective("win_condition", wc_descname, wc_desc)
- -- Table of terrestrial fields
- local fields = wl.Game().map.terrestrial_fields
-
-- The function to calculate the current points.
local _last_time_calculated = -100000
- local _plrpoints = {}
+ local playerpoints = {}
local function _calc_points()
- local game = wl.Game()
if _last_time_calculated > game.time - 5000 then
- return _plrpoints
- end
-
- -- clear out the table. We count afresh.
- for k,v in pairs(_plrpoints) do
- _plrpoints[k] = 0
- end
-
- -- Insert all players who are still in the game.
- for idx,plr in ipairs(plrs) do
- _plrpoints[plr.number] = 0
- end
-
- for idf,f in ipairs(fields) do
- -- check if field is owned by a player
- local owner = f.owner
- if owner and not owner.defeated then
- owner = owner.number
- -- check if field has an immovable
- local imm = f.immovable
- if imm then
- -- check if immovable is a tree
- if imm:has_attribute("tree") then
- _plrpoints[owner] = _plrpoints[owner] + 1
- end
- end
- end
- end
-
+ return
+ end
+
+ playerpoints = count_owned_valuable_fields_for_all_players(plrs, "tree")
_last_time_calculated = game.time
- return _plrpoints
end
local function _send_state(remaining_time, plrs, show_popup)
- local playerpoints = _calc_points()
+ _calc_points()
local msg = format_remaining_time(remaining_time) .. vspace(8) .. game_status.body
for idx,plr in ipairs(plrs) do
@@ -92,7 +66,7 @@
end
local function _game_over(plrs)
- local playerpoints = _calc_points()
+ _calc_points()
local points = {}
for idx,plr in ipairs(plrs) do
points[#points + 1] = { plr, playerpoints[plr.number] }
@@ -131,8 +105,8 @@
name = wc_trees_owned,
pic = "images/wui/stats/genstats_trees.png",
calculator = function(p)
- local pts = _calc_points(p)
- return pts[p.number]
+ _calc_points(p)
+ return playerpoints[p.number] or 0
end,
}
=== modified file 'src/game_io/game_cmd_queue_packet.cc'
--- src/game_io/game_cmd_queue_packet.cc 2019-02-23 11:00:49 +0000
+++ src/game_io/game_cmd_queue_packet.cc 2019-03-09 11:05:59 +0000
@@ -98,6 +98,10 @@
while (!p.empty()) {
const CmdQueue::CmdItem& it = p.top();
+ if (it.cmd->duetime() > time) {
+ // Time is the primary sorting key, so we can't have any additional commands in this queue for this time
+ break;
+ }
if (it.cmd->duetime() == time) {
if (upcast(GameLogicCommand, cmd, it.cmd)) {
// The id (aka command type)
=== modified file 'src/logic/game.cc'
--- src/logic/game.cc 2019-03-02 10:34:39 +0000
+++ src/logic/game.cc 2019-03-09 11:05:59 +0000
@@ -314,10 +314,14 @@
// Check for win_conditions
if (!settings.scenario) {
+ loader_ui->step(_("Initializing game…"));
std::unique_ptr<LuaTable> table(lua().run_script(settings.win_condition_script));
- get_ibase()->log_message(_("Initializing game…"));
table->do_not_warn_about_unaccessed_keys();
win_condition_displayname_ = table->get_string("name");
+ if (table->has_key<std::string>("init")) {
+ std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("init");
+ cr->resume();
+ }
std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("func");
enqueue_command(new CmdLuaCoroutine(get_gametime() + 100, std::move(cr)));
} else {
=== modified file 'src/logic/map.cc'
--- src/logic/map.cc 2019-03-02 09:31:11 +0000
+++ src/logic/map.cc 2019-03-09 11:05:59 +0000
@@ -257,8 +257,11 @@
}
}
-std::set<FCoords> Map::calculate_all_conquerable_fields() const {
- std::set<FCoords> result;
+size_t Map::count_all_conquerable_fields() {
+ if (!valuable_fields_.empty()) {
+ // Already calculated
+ return valuable_fields_.size();
+ }
std::set<FCoords> coords_to_check;
@@ -267,17 +270,17 @@
// If we don't have the given coordinates yet, walk the map and collect conquerable fields,
// initialized with the given radius around the coordinates
- const auto walk_starting_coords = [this, &result, &coords_to_check](
+ const auto walk_starting_coords = [this, &coords_to_check](
const Coords& coords, int radius) {
FCoords fcoords = get_fcoords(coords);
// We already have these coordinates
- if (result.count(fcoords) == 1) {
+ if (valuable_fields_.count(fcoords) == 1) {
return;
}
// Add starting field
- result.insert(fcoords);
+ valuable_fields_.insert(fcoords);
// Add outer land coordinates around the starting field for the given radius
std::unique_ptr<Widelands::HollowArea<>> hollow_area(
@@ -317,8 +320,8 @@
// We do the caps check first, because the comparison is faster than the container
// check
- if ((fcoords.field->maxcaps() & MOVECAPS_WALK) && (result.count(fcoords) == 0)) {
- result.insert(fcoords);
+ if ((fcoords.field->maxcaps() & MOVECAPS_WALK) && (valuable_fields_.count(fcoords) == 0)) {
+ valuable_fields_.insert(fcoords);
coords_to_check.insert(fcoords);
}
} while (map_region->advance(*this));
@@ -342,23 +345,44 @@
}
}
- log("%" PRIuS " found ... ", result.size());
- return result;
+ log("%" PRIuS " found ... ", valuable_fields_.size());
+ return valuable_fields_.size();
}
-std::set<FCoords> Map::calculate_all_fields_excluding_caps(NodeCaps caps) const {
+size_t Map::count_all_fields_excluding_caps(NodeCaps caps) {
+ if (!valuable_fields_.empty()) {
+ // Already calculated
+ return valuable_fields_.size();
+ }
+
log("Collecting valuable fields ... ");
ScopedTimer timer("took %ums");
- std::set<FCoords> result;
for (MapIndex i = 0; i < max_index(); ++i) {
Field& field = fields_[i];
if (!(field.nodecaps() & caps)) {
- result.insert(get_fcoords(field));
- }
- }
-
- log("%" PRIuS " found ... ", result.size());
+ valuable_fields_.insert(get_fcoords(field));
+ }
+ }
+
+ log("%" PRIuS " found ... ", valuable_fields_.size());
+ return valuable_fields_.size();
+}
+
+std::map<PlayerNumber, size_t> Map::count_owned_valuable_fields(const std::string& immovable_attribute) const {
+ std::map<PlayerNumber, size_t> result;
+ const bool use_attribute = !immovable_attribute.empty();
+ const uint32_t attribute_id = use_attribute ? MapObjectDescr::get_attribute_id(immovable_attribute) : 0U;
+ for (const FCoords& fcoords : valuable_fields_) {
+ if (use_attribute) {
+ const BaseImmovable* imm = fcoords.field->get_immovable();
+ if (imm != nullptr && imm->has_attribute(attribute_id)) {
+ result[fcoords.field->get_owned_by()] += 1;
+ }
+ } else {
+ result[fcoords.field->get_owned_by()] += 1;
+ }
+ }
return result;
}
=== modified file 'src/logic/map.h'
--- src/logic/map.h 2019-02-26 05:56:01 +0000
+++ src/logic/map.h 2019-03-09 11:05:59 +0000
@@ -165,11 +165,25 @@
void recalc_whole_map(const World& world);
void recalc_for_field_area(const World& world, Area<FCoords>);
- /// Calculates and returns a list of the fields that could be conquered by a player throughout a
- /// game. Useful for territorial win conditions.
- std::set<FCoords> calculate_all_conquerable_fields() const;
- /// Calculates and returns a list of the fields that do not have the given caps.
- std::set<FCoords> calculate_all_fields_excluding_caps(NodeCaps caps) const;
+ /**
+ * Calculates and returns a list of the fields that could be conquered by a player throughout a game.
+ * Useful for territorial win conditions.
+ * Returns the amount of valuable fields.
+ */
+ size_t count_all_conquerable_fields();
+
+ /**
+ * If the valuable fields are empty, calculates which fields do not have the given caps and adds the to the list of valuable fields.
+ * Useful for win conditions.
+ * Returns the amount of valuable fields.
+ */
+ size_t count_all_fields_excluding_caps(NodeCaps caps);
+
+ /**
+ * Counts the valuable fields that are owned by each player. Only players that currently own a field are added.
+ * Returns a map of <player number, number of owned fields>.
+ */
+ std::map<PlayerNumber, size_t> count_owned_valuable_fields(const std::string& immovable_attribute) const;
/***
* Ensures that resources match their adjacent terrains.
@@ -438,6 +452,10 @@
return &objectives_;
}
+ std::set<FCoords>* mutable_valuable_fields() {
+ return &valuable_fields_;
+ }
+
/// Returns the military influence on a location from an area.
MilitaryInfluence calc_influence(Coords, Area<>) const;
@@ -535,6 +553,9 @@
Objectives objectives_;
+ // Fields that are important for the player to own in a win condition
+ std::set<FCoords> valuable_fields_;
+
MapVersion map_version_;
};
=== modified file 'src/logic/player.cc'
--- src/logic/player.cc 2019-02-23 11:00:49 +0000
+++ src/logic/player.cc 2019-03-09 11:05:59 +0000
@@ -253,6 +253,15 @@
return &other != this && (!team_number_ || team_number_ != other.team_number_);
}
+bool Player::is_defeated() const {
+ for (const auto& economy : economies()) {
+ if (!economy.second->warehouses().empty()) {
+ return false;
+ }
+ }
+ return true;
+}
+
void Player::AiPersistentState::initialize() {
colony_scan_area = kColonyScanStartArea;
trees_around_cutters = 0;
=== modified file 'src/logic/player.h'
--- src/logic/player.h 2019-02-23 11:00:49 +0000
+++ src/logic/player.h 2019-03-09 11:05:59 +0000
@@ -148,6 +148,11 @@
bool is_hostile(const Player&) const;
+ /**
+ * Returns whether the player lost the last warehouse.
+ */
+ bool is_defeated() const;
+
// For cheating
void set_see_all(bool const t) {
see_all_ = t;
=== modified file 'src/logic/playercommand.cc'
--- src/logic/playercommand.cc 2019-02-23 11:00:49 +0000
+++ src/logic/playercommand.cc 2019-03-09 11:05:59 +0000
@@ -450,14 +450,17 @@
ser.unsigned_32(serial);
}
-constexpr uint16_t kCurrentPacketVersionCmdFlagAction = 1;
+constexpr uint16_t kCurrentPacketVersionCmdFlagAction = 2;
void CmdFlagAction::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) {
try {
const uint16_t packet_version = fr.unsigned_16();
- if (packet_version == kCurrentPacketVersionCmdFlagAction) {
+ // TODO(GunChleoc): Savegame compatibility, remove after Build 21
+ if (packet_version >= 1 && packet_version <= kCurrentPacketVersionCmdFlagAction) {
PlayerCommand::read(fr, egbase, mol);
- fr.unsigned_8();
+ if (packet_version == 1) {
+ fr.unsigned_8();
+ }
serial = get_object_serial_or_zero<Flag>(fr.unsigned_32(), mol);
} else {
throw UnhandledVersionError(
@@ -472,9 +475,6 @@
fw.unsigned_16(kCurrentPacketVersionCmdFlagAction);
// Write base classes
PlayerCommand::write(fw, egbase, mos);
- // Now action
- fw.unsigned_8(0);
-
// Now serial
fw.unsigned_32(mos.get_object_file_index_or_zero(egbase.objects().get_object(serial)));
}
=== modified file 'src/map_io/CMakeLists.txt'
--- src/map_io/CMakeLists.txt 2017-11-20 13:50:51 +0000
+++ src/map_io/CMakeLists.txt 2019-03-09 11:05:59 +0000
@@ -87,6 +87,8 @@
map_terrain_packet.h
map_version_packet.cc
map_version_packet.h
+ map_wincondition_packet.cc
+ map_wincondition_packet.h
DEPENDS
base_exceptions
base_log
=== modified file 'src/map_io/map_resources_packet.cc'
--- src/map_io/map_resources_packet.cc 2019-02-23 11:00:49 +0000
+++ src/map_io/map_resources_packet.cc 2019-03-09 11:05:59 +0000
@@ -19,7 +19,10 @@
#include "map_io/map_resources_packet.h"
+#include <boost/algorithm/string.hpp>
+
#include "base/log.h"
+#include "base/scoped_timer.h"
#include "io/fileread.h"
#include "io/filewrite.h"
#include "logic/editor_game_base.h"
@@ -31,7 +34,7 @@
namespace Widelands {
-constexpr uint16_t kCurrentPacketVersion = 1;
+constexpr uint16_t kCurrentPacketVersion = 2;
void MapResourcesPacket::read(FileSystem& fs,
EditorGameBase& egbase,
@@ -44,7 +47,7 @@
try {
const uint16_t packet_version = fr.unsigned_16();
- if (packet_version == kCurrentPacketVersion) {
+ if (packet_version >= 1 && packet_version <= kCurrentPacketVersion) {
int32_t const nr_res = fr.unsigned_16();
if (world.get_nr_resources() < nr_res)
log("WARNING: Number of resources in map (%i) is bigger than in world "
@@ -59,18 +62,51 @@
int32_t const res = world.get_resource(resource_name.c_str());
if (res == Widelands::INVALID_INDEX)
throw GameDataError(
- "resource '%s' exists in map but not in world", resource_name.c_str());
+ "MapResourcesPacket: Resource '%s' exists in map but not in world", resource_name.c_str());
smap[id] = res;
}
- for (uint16_t y = 0; y < map->get_height(); ++y) {
- for (uint16_t x = 0; x < map->get_width(); ++x) {
- DescriptionIndex const id = fr.unsigned_8();
- ResourceAmount const amount = fr.unsigned_8();
- ResourceAmount const start_amount = fr.unsigned_8();
- const auto fcoords = map->get_fcoords(Coords(x, y));
- map->initialize_resources(fcoords, smap[id], start_amount);
- map->set_resources(fcoords, amount);
+ if (packet_version == 1) {
+ // TODO(GunChleoc): Savegame compatibility, remove after Build21
+ for (uint16_t y = 0; y < map->get_height(); ++y) {
+ for (uint16_t x = 0; x < map->get_width(); ++x) {
+ DescriptionIndex const id = fr.unsigned_8();
+ ResourceAmount const amount = fr.unsigned_8();
+ ResourceAmount const start_amount = fr.unsigned_8();
+ const auto fcoords = map->get_fcoords(Coords(x, y));
+ map->initialize_resources(fcoords, smap[id], start_amount);
+ map->set_resources(fcoords, amount);
+ }
+ }
+ } else {
+ // String of entries of the form id-amount-initial_amount|
+ std::string input = fr.c_string();
+ std::vector<std::string> entries;
+ boost::split(entries, input, boost::is_any_of("|"));
+ // Check for correct size. We have a dangling |, so we expect 1 extra entry.
+ if (static_cast<int>((entries.size() - 1)) != (map->get_height() * map->get_width())) {
+ throw GameDataError(
+ "MapResourcesPacket: There should be map resources for %d fields, but we have %" PRIuS,
+ map->get_height() * map->get_width(),
+ entries.size() - 1);
+ }
+ auto iterator = entries.begin();
+ for (uint16_t y = 0; y < map->get_height(); ++y) {
+ for (uint16_t x = 0; x < map->get_width(); ++x) {
+ std::vector<std::string> addme;
+ boost::split(addme, *iterator++, boost::is_any_of("-"));
+ if (addme.size() != 3) {
+ throw GameDataError(
+ "MapResourcesPacket: Resource info for (%d,%d) should have 3 entries (ID, amount, starting amount), but we have %" PRIuS,
+ x, y, addme.size());
+ }
+ DescriptionIndex const id = static_cast<DescriptionIndex>(atoi(addme.at(0).c_str()));
+ ResourceAmount const amount = static_cast<ResourceAmount>(atoi(addme.at(1).c_str()));
+ ResourceAmount const initial_amount = static_cast<ResourceAmount>(atoi(addme.at(2).c_str()));
+ const auto fcoords = map->get_fcoords(Coords(x, y));
+ map->initialize_resources(fcoords, smap[id], initial_amount);
+ map->set_resources(fcoords, amount);
+ }
}
}
} else {
@@ -109,20 +145,15 @@
fw.c_string(res.name().c_str());
}
- // Now, all resouces as uint8_ts in order
- // - resource id
- // - amount
- for (uint16_t y = 0; y < map.get_height(); ++y) {
- for (uint16_t x = 0; x < map.get_width(); ++x) {
- const Field& f = map[Coords(x, y)];
- DescriptionIndex res = f.get_resources();
- ResourceAmount const amount = f.get_resources_amount();
- ResourceAmount const start_amount = f.get_initial_res_amount();
- fw.unsigned_8(res);
- fw.unsigned_8(amount);
- fw.unsigned_8(start_amount);
- }
+ // Performance: We write our resource information into a single string to reduce the number of write operations
+ std::ostringstream oss;
+ for (MapIndex i = 0; i < map.max_index(); ++i) {
+ Field& f = map[i];
+ oss << static_cast<unsigned int>(f.get_resources()) << "-";
+ oss << static_cast<unsigned int>(f.get_resources_amount()) << "-";
+ oss << static_cast<unsigned int>(f.get_initial_res_amount()) << "|";
}
+ fw.c_string(oss.str());
fw.write(fs, "binary/resource");
}
=== modified file 'src/map_io/map_saver.cc'
--- src/map_io/map_saver.cc 2019-02-23 11:00:49 +0000
+++ src/map_io/map_saver.cc 2019-03-09 11:05:59 +0000
@@ -58,6 +58,7 @@
#include "map_io/map_scripting_packet.h"
#include "map_io/map_terrain_packet.h"
#include "map_io/map_version_packet.h"
+#include "map_io/map_wincondition_packet.h"
namespace Widelands {
@@ -220,6 +221,14 @@
log("took %ums\n ", timer.ms_since_last_query());
if (is_game) {
+ // Map data used by win conditions
+ log("Writing Wincondition Data ... ");
+ {
+ MapWinconditionPacket p;
+ p.write(fs_, *egbase_.mutable_map(), *mos_);
+ }
+ log("took %ums\n ", timer.ms_since_last_query());
+
// DATA PACKETS
if (mos_->get_nr_flags()) {
log("Writing Flagdata Data ... ");
=== added file 'src/map_io/map_wincondition_packet.cc'
--- src/map_io/map_wincondition_packet.cc 1970-01-01 00:00:00 +0000
+++ src/map_io/map_wincondition_packet.cc 2019-03-09 11:05:59 +0000
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "map_io/map_wincondition_packet.h"
+
+#include "io/fileread.h"
+#include "io/filewrite.h"
+#include "logic/game_data_error.h"
+
+namespace Widelands {
+
+constexpr uint8_t kCurrentPacketVersion = 1;
+
+void MapWinconditionPacket::read(FileSystem& fs, Map& map, MapObjectLoader&) {
+ if (!fs.file_exists("binary/wincondition")) {
+ // This can be empty when win conditions don't need any special map data
+ return;
+ }
+
+ try {
+ FileRead fr;
+ fr.open(fs, "binary/wincondition");
+
+ const uint8_t packet_version = fr.unsigned_8();
+ if (packet_version == kCurrentPacketVersion) {
+ const size_t no_of_fields = fr.unsigned_32();
+ std::set<FCoords>* fields = map.mutable_valuable_fields();
+ fields->clear();
+
+ for (size_t i = 0; i < no_of_fields; ++i) {
+ const int32_t x = fr.signed_16();
+ const int32_t y = fr.signed_16();
+ fields->insert(map.get_fcoords(Coords(x, y)));
+ }
+ } else {
+ throw UnhandledVersionError("MapWinconditionPacket", packet_version, kCurrentPacketVersion);
+ }
+ } catch (const WException& e) {
+ throw GameDataError("win condition data: %s", e.what());
+ }
+}
+
+void MapWinconditionPacket::write(FileSystem& fs, Map& map, MapObjectSaver&) {
+ // We only write this packet if we have something interesting to write to it.
+ std::set<FCoords>& fields = *map.mutable_valuable_fields();
+ if (!fields.empty()) {
+ FileWrite fw;
+ fw.unsigned_8(kCurrentPacketVersion);
+
+ fw.unsigned_32(fields.size());
+ for (const auto& field : fields) {
+ fw.signed_16(field.x);
+ fw.signed_16(field.y);
+ }
+
+ fw.write(fs, "binary/wincondition");
+ }
+}
+} // namespace Widelands
=== added file 'src/map_io/map_wincondition_packet.h'
--- src/map_io/map_wincondition_packet.h 1970-01-01 00:00:00 +0000
+++ src/map_io/map_wincondition_packet.h 2019-03-09 11:05:59 +0000
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef WL_MAP_IO_MAP_WINCONDITION_PACKET_H
+#define WL_MAP_IO_MAP_WINCONDITION_PACKET_H
+
+#include "logic/map.h"
+
+namespace Widelands {
+
+/// The anything needed by win conditions
+struct MapWinconditionPacket {
+ void read(FileSystem&, Map& map, MapObjectLoader&);
+ void write(FileSystem&, Map& map, MapObjectSaver&);
+};
+} // namespace Widelands
+
+#endif // end of include guard: WL_MAP_IO_MAP_WINCONDITION_PACKET_H
=== modified file 'src/map_io/widelands_map_loader.cc'
--- src/map_io/widelands_map_loader.cc 2019-02-23 11:00:49 +0000
+++ src/map_io/widelands_map_loader.cc 2019-03-09 11:05:59 +0000
@@ -55,6 +55,7 @@
#include "map_io/map_scripting_packet.h"
#include "map_io/map_terrain_packet.h"
#include "map_io/map_version_packet.h"
+#include "map_io/map_wincondition_packet.h"
#include "map_io/tribes_legacy_lookup_table.h"
#include "map_io/world_legacy_lookup_table.h"
@@ -310,6 +311,14 @@
}
log("took %ums\n ", timer.ms_since_last_query());
+ // Map data used by win conditions.
+ log("Reading Wincondition Data ... ");
+ {
+ MapWinconditionPacket p;
+ p.read(*fs_, *egbase.mutable_map(), *mol_);
+ }
+ log("took %ums\n ", timer.ms_since_last_query());
+
// Objectives. They are not needed in the Editor, since they are fully
// defined through Lua scripting. They are also not required for a game,
// since they will be only be set after it has started.
=== modified file 'src/scripting/lua_game.cc'
--- src/scripting/lua_game.cc 2019-02-23 11:00:49 +0000
+++ src/scripting/lua_game.cc 2019-03-09 11:05:59 +0000
@@ -173,17 +173,7 @@
(RO) :const:`true` if this player was defeated, :const:`false` otherwise
*/
int LuaPlayer::get_defeated(lua_State* L) {
- Player& p = get(L, get_egbase(L));
- bool is_defeated = true;
-
- for (const auto& economy : p.economies()) {
- if (!economy.second->warehouses().empty()) {
- is_defeated = false;
- break;
- }
- }
-
- lua_pushboolean(L, is_defeated);
+ lua_pushboolean(L, get(L, get_egbase(L)).is_defeated());
return 1;
}
=== modified file 'src/scripting/lua_map.cc'
--- src/scripting/lua_map.cc 2019-02-26 12:02:52 +0000
+++ src/scripting/lua_map.cc 2019-03-09 11:05:59 +0000
@@ -1186,9 +1186,11 @@
*/
const char LuaMap::className[] = "Map";
const MethodType<LuaMap> LuaMap::Methods[] = {
+ METHOD(LuaMap, count_conquerable_fields), METHOD(LuaMap, count_terrestrial_fields),
+ METHOD(LuaMap, count_owned_valuable_fields),
METHOD(LuaMap, place_immovable), METHOD(LuaMap, get_field),
METHOD(LuaMap, recalculate), METHOD(LuaMap, recalculate_seafaring),
- METHOD(LuaMap, set_port_space), {nullptr, nullptr},
+ METHOD(LuaMap, set_port_space), {nullptr, nullptr},
};
const PropertyType<LuaMap> LuaMap::Properties[] = {
PROP_RO(LuaMap, allows_seafaring),
@@ -1197,8 +1199,6 @@
PROP_RO(LuaMap, width),
PROP_RO(LuaMap, height),
PROP_RO(LuaMap, player_slots),
- PROP_RO(LuaMap, conquerable_fields),
- PROP_RO(LuaMap, terrestrial_fields),
{nullptr, nullptr, nullptr},
};
@@ -1280,6 +1280,7 @@
return 1;
}
+
/* RST
.. attribute:: player_slots
@@ -1299,47 +1300,70 @@
return 1;
}
+
+/*
+ ==========================================================
+ LUA METHODS
+ ==========================================================
+ */
+
/* RST
- .. attribute:: conquerable_fields
+ .. method:: count_conquerable_fields()
(RO) Calculates and returns all reachable fields that a player could build on.
- **Note:** This function is expensive, so call it seldom.
-*/
-int LuaMap::get_conquerable_fields(lua_State* L) {
- lua_newtable(L);
- int counter = 0;
- for (const Widelands::FCoords& fcoords :
- get_egbase(L).map().calculate_all_conquerable_fields()) {
- lua_pushinteger(L, ++counter);
- to_lua<LuaMaps::LuaField>(L, new LuaMaps::LuaField(fcoords));
- lua_settable(L, -3);
- }
- return 1;
-}
-
-/* RST
- .. attribute:: terrestrial_fields
-
- (RO) Calculates and returns all fields that are not swimmable.
-*/
-int LuaMap::get_terrestrial_fields(lua_State* L) {
- lua_newtable(L);
- int counter = 0;
- for (const Widelands::FCoords& fcoords :
- get_egbase(L).map().calculate_all_fields_excluding_caps(MOVECAPS_SWIM)) {
- lua_pushinteger(L, ++counter);
- to_lua<LuaMaps::LuaField>(L, new LuaMaps::LuaField(fcoords));
- lua_settable(L, -3);
- }
- return 1;
-}
-
-/*
- ==========================================================
- LUA METHODS
- ==========================================================
- */
+ **Note:** The fields are only calculated afresh when this is called for the first time.
+
+ :returns: An integer with the amount of fields.
+*/
+// NOCOM document
+int LuaMap::count_conquerable_fields(lua_State* L) {
+ lua_pushinteger(L, get_egbase(L).mutable_map()->count_all_conquerable_fields());
+ return 1;
+}
+
+/* RST
+ .. method:: count_terrestrial_fields()
+
+ (RO) Counts all fields that are not swimmable.
+
+ **Note:** The fields are only calculated afresh when this is called for the first time.
+
+ :returns: An integer with the amount of fields.
+*/
+int LuaMap::count_terrestrial_fields(lua_State* L) {
+ lua_pushinteger(L, get_egbase(L).mutable_map()->count_all_fields_excluding_caps(MOVECAPS_SWIM));
+ return 1;
+}
+
+
+/* RST
+ .. method:: count_owned_valuable_fields([immovable_attribute])
+
+ (RO) Counts the number of owned valuable fields for all players.
+
+ :arg name: *Optional*. If this is set, only count fields that have an
+ immovable with the given atttribute.
+ :type name: :class:`string`
+
+ :returns: A table mapping player numbers to their number of owned fields.
+*/
+int LuaMap::count_owned_valuable_fields(lua_State* L) {
+ if (lua_gettop(L) > 2) {
+ report_error(L, "Does not take more than one argument.");
+ }
+ const std::string attribute = lua_gettop(L) == 2 ? luaL_checkstring(L, -1) : "";
+
+ lua_newtable(L);
+ for (const auto& fieldinfo :
+ get_egbase(L).map().count_owned_valuable_fields(attribute)) {
+ lua_pushinteger(L, fieldinfo.first);
+ lua_pushinteger(L, fieldinfo.second);
+ lua_settable(L, -3);
+ }
+ return 1;
+}
+
/* RST
.. method:: place_immovable(name, field, from_where)
=== modified file 'src/scripting/lua_map.h'
--- src/scripting/lua_map.h 2019-02-24 22:50:04 +0000
+++ src/scripting/lua_map.h 2019-03-09 11:05:59 +0000
@@ -92,12 +92,14 @@
int get_width(lua_State*);
int get_height(lua_State*);
int get_player_slots(lua_State*);
- int get_conquerable_fields(lua_State*);
- int get_terrestrial_fields(lua_State*);
+
/*
* Lua methods
*/
+ int count_conquerable_fields(lua_State*);
+ int count_terrestrial_fields(lua_State*);
+ int count_owned_valuable_fields(lua_State*);
int place_immovable(lua_State*);
int get_field(lua_State*);
int recalculate(lua_State*);
Follow ups
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: GunChleoc, 2019-03-11
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: GunChleoc, 2019-03-11
-
[Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: noreply, 2019-03-11
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: Toni Förster, 2019-03-11
-
[Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: bunnybot, 2019-03-11
-
[Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: bunnybot, 2019-03-11
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: Toni Förster, 2019-03-11
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: GunChleoc, 2019-03-11
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: Toni Förster, 2019-03-11
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: GunChleoc, 2019-03-11
-
[Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: GunChleoc, 2019-03-11
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: kaputtnik, 2019-03-11
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: GunChleoc, 2019-03-11
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: Toni Förster, 2019-03-11
-
[Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: bunnybot, 2019-03-10
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: GunChleoc, 2019-03-10
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: Toni Förster, 2019-03-10
-
[Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: bunnybot, 2019-03-09
-
Re: [Merge] lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
From: Toni Förster, 2019-03-09