From b5bbeb47febc823517a5baba4eca66f32fb3609c Mon Sep 17 00:00:00 2001 From: hop311 Date: Sat, 4 Nov 2023 18:44:38 +0000 Subject: Cross-platform file lookup (case and separator) --- src/openvic-simulation/country/Country.cpp | 19 +- src/openvic-simulation/country/Country.hpp | 4 +- src/openvic-simulation/dataloader/Dataloader.cpp | 339 +++++++++++++-------- src/openvic-simulation/dataloader/Dataloader.hpp | 26 +- src/openvic-simulation/map/Map.cpp | 8 +- src/openvic-simulation/map/Province.cpp | 26 +- src/openvic-simulation/map/Province.hpp | 9 +- src/openvic-simulation/military/Deployment.cpp | 16 +- src/openvic-simulation/military/LeaderTrait.hpp | 2 +- src/openvic-simulation/pop/Culture.cpp | 2 +- .../types/IdentifierRegistry.hpp | 42 ++- src/openvic-simulation/utility/Logger.cpp | 34 --- src/openvic-simulation/utility/Logger.hpp | 21 +- src/openvic-simulation/utility/StringUtils.hpp | 68 ++++- 14 files changed, 376 insertions(+), 240 deletions(-) delete mode 100644 src/openvic-simulation/utility/Logger.cpp (limited to 'src/openvic-simulation') diff --git a/src/openvic-simulation/country/Country.cpp b/src/openvic-simulation/country/Country.cpp index 9e244c1..6ad13ee 100644 --- a/src/openvic-simulation/country/Country.cpp +++ b/src/openvic-simulation/country/Country.cpp @@ -78,6 +78,12 @@ bool CountryManager::add_country( Logger::error("Invalid country identifier - empty!"); return false; } + if (!valid_basic_identifier(identifier)) { + Logger::error( + "Invalid country identifier: ", identifier, " (can only contain alphanumeric characters and underscores)" + ); + return false; + } if (colour > MAX_COLOUR_RGB) { Logger::error("Invalid country colour for ", identifier, ": ", colour_to_hex_string(colour)); return false; @@ -93,14 +99,13 @@ bool CountryManager::add_country( }); } -bool CountryManager::load_countries( - GameManager const& game_manager, Dataloader const& dataloader, fs::path const& countries_dir, ast::NodeCPtr root -) { +bool CountryManager::load_countries(GameManager const& game_manager, Dataloader const& dataloader, ast::NodeCPtr root) { + static constexpr std::string_view common_dir = "common/"; bool is_dynamic = false; const bool ret = expect_dictionary_reserve_length( countries, - [this, &game_manager, &is_dynamic, &dataloader, &countries_dir](std::string_view key, ast::NodeCPtr value) -> bool { + [this, &game_manager, &is_dynamic, &dataloader](std::string_view key, ast::NodeCPtr value) -> bool { if (key == "dynamic_tags") { return expect_bool([&is_dynamic](bool val) -> bool { if (val == is_dynamic) { @@ -115,10 +120,12 @@ bool CountryManager::load_countries( })(value); } if (expect_string( - [this, &game_manager, is_dynamic, &dataloader, &countries_dir, &key](std::string_view filepath) -> bool { + [this, &game_manager, is_dynamic, &dataloader, &key](std::string_view filepath) -> bool { if (load_country_data_file( game_manager, key, is_dynamic, - Dataloader::parse_defines(dataloader.lookup_file_case_insensitive(countries_dir / filepath)).get_file_node() + Dataloader::parse_defines( + dataloader.lookup_file(StringUtils::append_string_views(common_dir, filepath)) + ).get_file_node() )) { return true; } diff --git a/src/openvic-simulation/country/Country.hpp b/src/openvic-simulation/country/Country.hpp index 7754a0b..50cca04 100644 --- a/src/openvic-simulation/country/Country.hpp +++ b/src/openvic-simulation/country/Country.hpp @@ -103,9 +103,7 @@ namespace OpenVic { ); IDENTIFIER_REGISTRY_ACCESSORS_CUSTOM_PLURAL(country, countries) - bool load_countries( - GameManager const& game_manager, Dataloader const& dataloader, fs::path const& countries_dir, ast::NodeCPtr root - ); + bool load_countries(GameManager const& game_manager, Dataloader const& dataloader, ast::NodeCPtr root); bool load_country_data_file( GameManager const& game_manager, std::string_view name, bool is_dynamic, ast::NodeCPtr root ); diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp index e98e63b..0174eb3 100644 --- a/src/openvic-simulation/dataloader/Dataloader.cpp +++ b/src/openvic-simulation/dataloader/Dataloader.cpp @@ -34,6 +34,10 @@ using namespace OpenVic; using namespace OpenVic::NodeTools; using namespace ovdl; +using StringUtils::append_string_views; + +#define FILESYSTEM_CASE_INSENSITIVE (defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__))) +#define FILESYSTEM_NEEDS_FORWARD_SLASHES (!defined(_WIN32)) static constexpr bool path_equals_case_insensitive(std::string_view lhs, std::string_view rhs) { constexpr auto ichar_equals = [](unsigned char l, unsigned char r) { @@ -44,7 +48,7 @@ static constexpr bool path_equals_case_insensitive(std::string_view lhs, std::st // Windows and Mac by default act like case insensitive filesystems static constexpr bool path_equals(std::string_view lhs, std::string_view rhs) { -#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) +#if FILESYSTEM_CASE_INSENSITIVE return path_equals_case_insensitive(lhs, rhs); #else return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); @@ -74,13 +78,13 @@ static bool filename_equals(const is_filename auto& lhs, const is_filename auto& static fs::path _search_for_game_path(fs::path hint_path = {}) { // Apparently max amount of steam libraries is 8, if incorrect please correct it to the correct max amount - constexpr int max_amount_of_steam_libraries = 8; - constexpr std::string_view Victoria_2_folder = "Victoria 2"; - constexpr std::string_view v2_game_exe = "v2game.exe"; - constexpr std::string_view steamapps = "steamapps"; - constexpr std::string_view libraryfolders = "libraryfolders.vdf"; - constexpr std::string_view vic2_appmanifest = "appmanifest_42960.acf"; - constexpr std::string_view common_folder = "common"; + static constexpr int max_amount_of_steam_libraries = 8; + static constexpr std::string_view Victoria_2_folder = "Victoria 2"; + static constexpr std::string_view v2_game_exe = "v2game.exe"; + static constexpr std::string_view steamapps = "steamapps"; + static constexpr std::string_view libraryfolders = "libraryfolders.vdf"; + static constexpr std::string_view vic2_appmanifest = "appmanifest_42960.acf"; + static constexpr std::string_view common_folder = "common"; std::error_code error_code; @@ -364,23 +368,33 @@ bool Dataloader::set_roots(path_vector_t const& new_roots) { return ret; } -fs::path Dataloader::lookup_file(fs::path const& path, bool print_error) const { +fs::path Dataloader::lookup_file(std::string_view path, bool print_error) const { +#if FILESYSTEM_NEEDS_FORWARD_SLASHES + /* Back-slashes need to be converted into forward-slashes */ + const std::string forward_slash_path { + StringUtils::make_forward_slash_path(StringUtils::remove_leading_slashes(path)) + }; + path = forward_slash_path; +#endif + + const fs::path filepath { path }; + +#if FILESYSTEM_CASE_INSENSITIVE + /* Case-insensitive filesystem */ for (fs::path const& root : roots) { - const fs::path composed = root / path; + const fs::path composed = root / filepath; if (fs::is_regular_file(composed)) { return composed; } } - if (print_error) { - Logger::error("Lookup for ", path, " failed!"); - } - return {}; -} - -fs::path Dataloader::lookup_file_case_insensitive(fs::path const& path, bool print_error) const { - const std::string filename = path.filename().string(); +#else + /* Case-sensitive filesystem */ + const std::string_view filename = StringUtils::get_filename(path); for (fs::path const& root : roots) { - const fs::path composed = root / path; + const fs::path composed = root / filepath; + if (fs::is_regular_file(composed)) { + return composed; + } std::error_code ec; for (fs::directory_entry const& entry : fs::directory_iterator { composed.parent_path(), ec }) { if (entry.is_regular_file()) { @@ -391,32 +405,44 @@ fs::path Dataloader::lookup_file_case_insensitive(fs::path const& path, bool pri } } } +#endif + if (print_error) { - Logger::error("Lookup for ", path, " failed!"); + Logger::error("Lookup for \"", path, "\" failed!"); } return {}; } -static bool contains_file_with_name(Dataloader::path_vector_t const& paths, fs::path const& name) { - for (fs::path const& path : paths) { - if (path.filename() == name) { - return true; - } - } - return false; -} - -Dataloader::path_vector_t Dataloader::lookup_files_in_dir(fs::path const& path, fs::path const& extension) const { +template _Equiv> +Dataloader::path_vector_t Dataloader::_lookup_files_in_dir(std::string_view path, fs::path const& extension) const { +#if FILESYSTEM_NEEDS_FORWARD_SLASHES + /* Back-slashes need to be converted into forward-slashes */ + const std::string forward_slash_path { + StringUtils::make_forward_slash_path(StringUtils::remove_leading_slashes(path)) + }; + path = forward_slash_path; +#endif + const fs::path filepath { path }; + static constexpr _Equiv Equiv {}; path_vector_t ret; + size_t start_of_current_root_entries; for (fs::path const& root : roots) { - const fs::path composed = root / path; + start_of_current_root_entries = ret.size(); + const fs::path composed = root / filepath; std::error_code ec; - for (fs::directory_entry const& entry : fs::directory_iterator { composed, ec }) { + for (fs::directory_entry const& entry : _DirIterator { composed, ec }) { if (entry.is_regular_file()) { const fs::path file = entry; - if (extension.empty() || file.extension() == extension) { - if (!contains_file_with_name(ret, file.filename())) { + if ((extension.empty() || file.extension() == extension) && !Equiv(file, {})) { + size_t index = 0; + for (; index < ret.size() && !Equiv(file, ret[index]); ++index) {} + if (index >= ret.size()) { ret.push_back(file); + } else if (start_of_current_root_entries <= index) { + Logger::warning( + "Files in the same directory with conflicting names: ", ret[index], " (accepted) and ", + file, " (rejected)" + ); } } } @@ -425,11 +451,43 @@ Dataloader::path_vector_t Dataloader::lookup_files_in_dir(fs::path const& path, return ret; } -bool Dataloader::apply_to_files_in_dir( - fs::path const& path, fs::path const& extension, callback_t callback +struct EquivFilename { + bool operator()(fs::path const& lhs, fs::path const& rhs) const { + return lhs.filename() == rhs.filename(); + } +}; + +Dataloader::path_vector_t Dataloader::lookup_files_in_dir(std::string_view path, fs::path const& extension) const { + return _lookup_files_in_dir(path, extension); +} + +Dataloader::path_vector_t Dataloader::lookup_files_in_dir_recursive(std::string_view path, fs::path const& extension) const { + return _lookup_files_in_dir(path, extension); +} + +struct EquivBasicIdentifierPrefix { + bool operator()(fs::path const& lhs, fs::path const& rhs) const { + const std::string lhs_str = lhs.stem().string(); + const std::string rhs_str = rhs.stem().string(); + return extract_basic_identifier_prefix(lhs_str) == extract_basic_identifier_prefix(rhs_str); + } +}; + +Dataloader::path_vector_t Dataloader::lookup_basic_indentifier_prefixed_files_in_dir( + std::string_view path, fs::path const& extension ) const { + return _lookup_files_in_dir(path, extension); +} + +Dataloader::path_vector_t Dataloader::lookup_basic_indentifier_prefixed_files_in_dir_recursive( + std::string_view path, fs::path const& extension +) const { + return _lookup_files_in_dir(path, extension); +} + +bool Dataloader::apply_to_files(path_vector_t const& files, callback_t callback) const { bool ret = true; - for (fs::path const& file : lookup_files_in_dir(path, extension)) { + for (fs::path const& file : files) { if (!callback(file)) { Logger::error("Callback failed for file: ", file); ret = false; @@ -504,8 +562,9 @@ csv::Windows1252Parser Dataloader::parse_csv(fs::path const& path) { bool Dataloader::_load_pop_types( PopManager& pop_manager, UnitManager const& unit_manager, GoodManager const& good_manager ) const { - static const fs::path pop_type_directory = "poptypes"; - const bool ret = apply_to_files_in_dir(pop_type_directory, ".txt", + static constexpr std::string_view pop_type_directory = "poptypes"; + const bool ret = apply_to_files( + lookup_files_in_dir(pop_type_directory, ".txt"), [&pop_manager, &unit_manager, &good_manager](fs::path const& file) -> bool { return pop_manager.load_pop_type_file( file.stem().string(), unit_manager, good_manager, parse_defines(file).get_file_node() @@ -517,36 +576,40 @@ bool Dataloader::_load_pop_types( } bool Dataloader::_load_units(UnitManager& unit_manager, GoodManager const& good_manager) const { - static const fs::path units_directory = "units"; - const bool ret = apply_to_files_in_dir(units_directory, ".txt", + static constexpr std::string_view units_directory = "units"; + const bool ret = apply_to_files( + lookup_files_in_dir(units_directory, ".txt"), [&unit_manager, &good_manager](fs::path const& file) -> bool { return unit_manager.load_unit_file(good_manager, parse_defines(file).get_file_node()); - }); + } + ); unit_manager.lock_units(); return ret; } -bool Dataloader::_load_history(GameManager& game_manager) const { - static const fs::path country_history_directory = "history/countries"; - static const fs::path province_history_directory = "history/provinces"; +bool Dataloader::_load_history(GameManager& game_manager, bool unused_history_file_warnings) const { /* Country History */ - bool ret = apply_to_files_in_dir(country_history_directory, ".txt", [this, &game_manager](fs::path const& file) -> bool { - const std::string filename = file.stem().string(); - // TODO - standardise rules on country idenifiers characters (probably letters + underscore) and enforce them - const size_t len = std::min(std::min(filename.find(" "), filename.find("-")), filename.length()); - const std::string_view country_id { filename.data(), len }; - - Country const* country = game_manager.get_country_manager().get_country_by_identifier(country_id); - if (country == nullptr) { - Logger::warning("Found history file for non-existent country: ", country_id); - return true; - } + static constexpr std::string_view country_history_directory = "history/countries"; + bool ret = apply_to_files( + lookup_basic_indentifier_prefixed_files_in_dir(country_history_directory, ".txt"), + [this, &game_manager, unused_history_file_warnings](fs::path const& file) -> bool { + const std::string filename = file.stem().string(); + const std::string_view country_id = extract_basic_identifier_prefix(filename); + + Country const* country = game_manager.get_country_manager().get_country_by_identifier(country_id); + if (country == nullptr) { + if (unused_history_file_warnings) { + Logger::warning("Found history file for non-existent country: ", country_id); + } + return true; + } - return game_manager.get_history_manager().get_country_manager().load_country_history_file( - game_manager, *this, *country, parse_defines(lookup_file(file)).get_file_node() - ); - }); + return game_manager.get_history_manager().get_country_manager().load_country_history_file( + game_manager, *this, *country, parse_defines(file).get_file_node() + ); + } + ); game_manager.get_history_manager().get_country_manager().lock_country_histories(); { @@ -558,53 +621,51 @@ bool Dataloader::_load_history(GameManager& game_manager) const { } /* Province History */ - for (auto root : roots) { - const fs::path path = root / province_history_directory; - std::error_code ec; - for (fs::directory_entry const& entry : fs::directory_iterator { path, ec }) { - if (entry.is_directory()) { - bool ret = apply_to_files_in_dir(entry, ".txt", [this, &game_manager](fs::path const& file) -> bool { - const std::string filename = file.stem().string(); - const size_t len = std::min(filename.find(" "), filename.length()); - const std::string_view province_id { filename.data(), len }; - - Province const* province = game_manager.get_map().get_province_by_identifier(province_id); - if (province == nullptr) { - Logger::warning("Found history file for non-existent province: ", province_id); - return true; - } - - return game_manager.get_history_manager().get_province_manager().load_province_history_file( - game_manager, *province, parse_defines(lookup_file(file)).get_file_node() - ); - }); + static constexpr std::string_view province_history_directory = "history/provinces"; + ret &= apply_to_files( + lookup_basic_indentifier_prefixed_files_in_dir_recursive(province_history_directory, ".txt"), + [this, &game_manager, unused_history_file_warnings](fs::path const& file) -> bool { + const std::string filename = file.stem().string(); + const std::string_view province_id = extract_basic_identifier_prefix(filename); + + Province const* province = game_manager.get_map().get_province_by_identifier(province_id); + if (province == nullptr) { + if (unused_history_file_warnings) { + Logger::warning("Found history file for non-existent province: ", province_id); + } + return true; } + + return game_manager.get_history_manager().get_province_manager().load_province_history_file( + game_manager, *province, parse_defines(file).get_file_node() + ); } - } + ); game_manager.get_history_manager().get_province_manager().lock_province_histories(); return ret; } bool Dataloader::_load_map_dir(GameManager& game_manager) const { - static const fs::path map_directory = "map"; + static constexpr std::string_view map_directory = "map/"; Map& map = game_manager.get_map(); - static const fs::path defaults_filename = "default.map"; - static const std::string default_definitions = "definition.csv"; - static const std::string default_provinces = "provinces.bmp"; - static const std::string default_positions = "positions.txt"; - static const std::string default_terrain = "terrain.bmp"; - static const std::string default_rivers = "rivers.bmp"; - static const std::string default_terrain_definition = "terrain.txt"; - static const std::string default_tree_definition = "trees.txt"; - static const std::string default_continent = "continent.txt"; - static const std::string default_adjacencies = "adjacencies.csv"; - static const std::string default_region = "region.txt"; - static const std::string default_region_sea = "region_sea.txt"; - static const std::string default_province_flag_sprite = "province_flag_sprites"; - - const v2script::Parser parser = parse_defines(lookup_file(map_directory / defaults_filename)); + static constexpr std::string_view defaults_filename = "default.map"; + static constexpr std::string_view default_definitions = "definition.csv"; + static constexpr std::string_view default_provinces = "provinces.bmp"; + static constexpr std::string_view default_positions = "positions.txt"; + static constexpr std::string_view default_terrain = "terrain.bmp"; + static constexpr std::string_view default_rivers = "rivers.bmp"; + static constexpr std::string_view default_terrain_definition = "terrain.txt"; + static constexpr std::string_view default_tree_definition = "trees.txt"; + static constexpr std::string_view default_continent = "continent.txt"; + static constexpr std::string_view default_adjacencies = "adjacencies.csv"; + static constexpr std::string_view default_region = "region.txt"; + static constexpr std::string_view default_region_sea = "region_sea.txt"; + static constexpr std::string_view default_province_flag_sprite = "province_flag_sprites"; + + const v2script::Parser parser = + parse_defines(lookup_file(append_string_views(map_directory, defaults_filename))); std::vector water_province_identifiers; @@ -658,20 +719,24 @@ bool Dataloader::_load_map_dir(GameManager& game_manager) const { Logger::error("Failed to load map default file!"); } - if (!map.load_province_definitions(parse_csv(lookup_file(map_directory / definitions)).get_lines())) { + if (!map.load_province_definitions( + parse_csv(lookup_file(append_string_views(map_directory, definitions))).get_lines() + )) { Logger::error("Failed to load province definitions file!"); ret = false; } if (!map.load_province_positions( game_manager.get_economy_manager().get_building_manager(), - parse_defines(lookup_file(map_directory / positions)).get_file_node() + parse_defines(lookup_file(append_string_views(map_directory, positions))).get_file_node() )) { Logger::error("Failed to load province positions file!"); ret = false; } - if (!map.load_region_file(parse_defines(lookup_file(map_directory / region)).get_file_node())) { + if (!map.load_region_file( + parse_defines(lookup_file(append_string_views(map_directory, region))).get_file_node() + )) { Logger::error("Failed to load region file!"); ret = false; } @@ -682,18 +747,24 @@ bool Dataloader::_load_map_dir(GameManager& game_manager) const { } if (!map.get_terrain_type_manager().load_terrain_types( - game_manager.get_modifier_manager(), parse_defines(lookup_file(map_directory / terrain_definition)).get_file_node() + game_manager.get_modifier_manager(), + parse_defines(lookup_file(append_string_views(map_directory, terrain_definition))).get_file_node() )) { Logger::error("Failed to load terrain types!"); ret = false; } - if (!map.load_map_images(lookup_file(map_directory / provinces), lookup_file(map_directory / terrain), false)) { + if (!map.load_map_images( + lookup_file(append_string_views(map_directory, provinces)), + lookup_file(append_string_views(map_directory, terrain)), false + )) { Logger::error("Failed to load map images!"); ret = false; } - if (!map.generate_and_load_province_adjacencies(parse_csv(lookup_file(map_directory / adjacencies)).get_lines())) { + if (!map.generate_and_load_province_adjacencies( + parse_csv(lookup_file(append_string_views(map_directory, adjacencies))).get_lines() + )) { Logger::error("Failed to generate and load province adjacencies!"); ret = false; } @@ -702,20 +773,20 @@ bool Dataloader::_load_map_dir(GameManager& game_manager) const { } bool Dataloader::load_defines(GameManager& game_manager) const { - static const fs::path defines_file = "common/defines.lua"; - static const fs::path buildings_file = "common/buildings.txt"; - static const fs::path bookmark_file = "common/bookmarks.txt"; - static const fs::path countries_file = "common/countries.txt"; - static const fs::path culture_file = "common/cultures.txt"; - static const fs::path goods_file = "common/goods.txt"; - static const fs::path governments_file = "common/governments.txt"; - static const fs::path graphical_culture_type_file = "common/graphicalculturetype.txt"; - static const fs::path ideology_file = "common/ideologies.txt"; - static const fs::path issues_file = "common/issues.txt"; - static const fs::path national_values_file = "common/nationalvalues.txt"; - static const fs::path production_types_file = "common/production_types.txt"; - static const fs::path religion_file = "common/religion.txt"; - static const fs::path leader_traits_file = "common/traits.txt"; + static const std::string defines_file = "common/defines.lua"; + static const std::string buildings_file = "common/buildings.txt"; + static const std::string bookmark_file = "common/bookmarks.txt"; + static const std::string countries_file = "common/countries.txt"; + static const std::string culture_file = "common/cultures.txt"; + static const std::string goods_file = "common/goods.txt"; + static const std::string governments_file = "common/governments.txt"; + static const std::string graphical_culture_type_file = "common/graphicalculturetype.txt"; + static const std::string ideology_file = "common/ideologies.txt"; + static const std::string issues_file = "common/issues.txt"; + static const std::string national_values_file = "common/nationalvalues.txt"; + static const std::string production_types_file = "common/production_types.txt"; + static const std::string religion_file = "common/religion.txt"; + static const std::string leader_traits_file = "common/traits.txt"; bool ret = true; @@ -815,12 +886,12 @@ bool Dataloader::load_defines(GameManager& game_manager) const { ret = false; } if (!game_manager.get_country_manager().load_countries( - game_manager, *this, countries_file.parent_path(), parse_defines(lookup_file(countries_file)).get_file_node() + game_manager, *this, parse_defines(lookup_file(countries_file)).get_file_node() )) { Logger::error("Failed to load countries!"); ret = false; } - if (!_load_history(game_manager)) { + if (!_load_history(game_manager, false)) { Logger::error("Failed to load history!"); ret = false; } @@ -828,14 +899,17 @@ bool Dataloader::load_defines(GameManager& game_manager) const { return ret; } -bool Dataloader::load_pop_history(GameManager& game_manager, fs::path const& path) const { - return apply_to_files_in_dir(path, ".txt", [&game_manager](fs::path const& file) -> bool { - return game_manager.get_map().expect_province_dictionary( - [&game_manager](Province& province, ast::NodeCPtr value) -> bool { - return province.load_pop_list(game_manager.get_pop_manager(), value); - } - )(parse_defines(file).get_file_node()); - }); +bool Dataloader::load_pop_history(GameManager& game_manager, std::string_view path) const { + return apply_to_files( + lookup_files_in_dir(path, ".txt"), + [&game_manager](fs::path const& file) -> bool { + return game_manager.get_map().expect_province_dictionary( + [&game_manager](Province& province, ast::NodeCPtr value) -> bool { + return province.load_pop_list(game_manager.get_pop_manager(), value); + } + )(parse_defines(file).get_file_node()); + } + ); } static bool _load_localisation_file(Dataloader::localisation_callback_t callback, std::vector const& lines) { @@ -855,8 +929,11 @@ static bool _load_localisation_file(Dataloader::localisation_callback_t callback return ret; } -bool Dataloader::load_localisation_files(localisation_callback_t callback, fs::path const& localisation_dir) const { - return apply_to_files_in_dir(localisation_dir, ".csv", [callback](fs::path path) -> bool { - return _load_localisation_file(callback, parse_csv(path).get_lines()); - }); +bool Dataloader::load_localisation_files(localisation_callback_t callback, std::string_view localisation_dir) const { + return apply_to_files( + lookup_files_in_dir(localisation_dir, ".csv"), + [callback](fs::path path) -> bool { + return _load_localisation_file(callback, parse_csv(path).get_lines()); + } + ); } diff --git a/src/openvic-simulation/dataloader/Dataloader.hpp b/src/openvic-simulation/dataloader/Dataloader.hpp index a8b8bb1..2123469 100644 --- a/src/openvic-simulation/dataloader/Dataloader.hpp +++ b/src/openvic-simulation/dataloader/Dataloader.hpp @@ -26,7 +26,13 @@ namespace OpenVic { bool _load_pop_types(PopManager& pop_manager, UnitManager const& unit_manager, GoodManager const& good_manager) const; bool _load_units(UnitManager& unit_manager, GoodManager const& good_manager) const; bool _load_map_dir(GameManager& game_manager) const; - bool _load_history(GameManager& game_manager) const; + bool _load_history(GameManager& game_manager, bool unused_history_file_warnings) const; + + /* _DirIterator is fs::directory_iterator or fs::recursive_directory_iterator. + * _Equiv is an equivalence relation with respect to which every found file shall be unique. + * If a file is equivalent to the empty path then it is not included. */ + template _Equiv> + path_vector_t _lookup_files_in_dir(std::string_view path, fs::path const& extension) const; public: static ovdl::v2script::Parser parse_defines(fs::path const& path); @@ -63,15 +69,17 @@ namespace OpenVic { /* REQUIREMENTS: * DAT-24 */ - fs::path lookup_file(fs::path const& path, bool print_error = true) const; - fs::path lookup_file_case_insensitive(fs::path const& path, bool print_error = true) const; - path_vector_t lookup_files_in_dir(fs::path const& path, fs::path const& extension) const; - bool apply_to_files_in_dir( - fs::path const& path, fs::path const& extension, NodeTools::callback_t callback + fs::path lookup_file(std::string_view path, bool print_error = true) const; + path_vector_t lookup_files_in_dir(std::string_view path, fs::path const& extension) const; + path_vector_t lookup_files_in_dir_recursive(std::string_view path, fs::path const& extension) const; + path_vector_t lookup_basic_indentifier_prefixed_files_in_dir(std::string_view path, fs::path const& extension) const; + path_vector_t lookup_basic_indentifier_prefixed_files_in_dir_recursive( + std::string_view path, fs::path const& extension ) const; + bool apply_to_files(path_vector_t const& files, NodeTools::callback_t callback) const; bool load_defines(GameManager& game_manager) const; - bool load_pop_history(GameManager& game_manager, fs::path const& path) const; + bool load_pop_history(GameManager& game_manager, std::string_view path) const; enum locale_t : size_t { English, French, German, Polish, Spanish, Italian, Swedish, @@ -85,7 +93,7 @@ namespace OpenVic { /* Args: key, locale, localisation */ using localisation_callback_t = NodeTools::callback_t; bool load_localisation_files( - localisation_callback_t callback, fs::path const& localisation_dir = "localisation" + localisation_callback_t callback, std::string_view localisation_dir = "localisation" ) const; private: @@ -97,6 +105,6 @@ namespace OpenVic { using hint_path_t = fs::path; using game_path_t = fs::path; - inline static std::unordered_map _cached_paths; + static inline std::unordered_map _cached_paths; }; } diff --git a/src/openvic-simulation/map/Map.cpp b/src/openvic-simulation/map/Map.cpp index b1aea2c..7e2213e 100644 --- a/src/openvic-simulation/map/Map.cpp +++ b/src/openvic-simulation/map/Map.cpp @@ -42,6 +42,12 @@ bool Map::add_province(std::string_view identifier, colour_t colour) { Logger::error("Invalid province identifier - empty!"); return false; } + if (!valid_basic_identifier(identifier)) { + Logger::error( + "Invalid province identifier: ", identifier, " (can only contain alphanumeric characters and underscores)" + ); + return false; + } if (colour == NULL_COLOUR || colour > MAX_COLOUR_RGB) { Logger::error("Invalid province colour for ", identifier, ": ", colour_to_hex_string(colour)); return false; @@ -483,7 +489,7 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain uint8_t const* terrain_data = terrain_bmp.get_pixel_data().data(); std::vector province_checklist(provinces.size()); - std::vector terrain_type_pixels_list(provinces.size()); + std::vector> terrain_type_pixels_list(provinces.size()); bool ret = true; std::unordered_set unrecognised_province_colours; diff --git a/src/openvic-simulation/map/Province.cpp b/src/openvic-simulation/map/Province.cpp index 66e4daf..77bf33f 100644 --- a/src/openvic-simulation/map/Province.cpp +++ b/src/openvic-simulation/map/Province.cpp @@ -131,31 +131,21 @@ Pop::pop_size_t Province::get_total_population() const { return total_population; } -distribution_t const& Province::get_pop_type_distribution() const { - return pop_types; -} - -distribution_t const& Province::get_culture_distribution() const { - return cultures; -} - -distribution_t const& Province::get_religion_distribution() const { - return religions; -} - /* REQUIREMENTS: * MAP-65, MAP-68, MAP-70, MAP-234 */ void Province::update_pops() { total_population = 0; - pop_types.clear(); - cultures.clear(); - religions.clear(); + pop_type_distribution.clear(); + ideology_distribution.clear(); + culture_distribution.clear(); + religion_distribution.clear(); for (Pop const& pop : pops) { total_population += pop.get_size(); - pop_types[&pop.get_type()] += pop.get_size(); - cultures[&pop.get_culture()] += pop.get_size(); - religions[&pop.get_religion()] += pop.get_size(); + pop_type_distribution[&pop.get_type()] += pop.get_size(); + //ideology_distribution[&pop.get_???()] += pop.get_size(); + culture_distribution[&pop.get_culture()] += pop.get_size(); + religion_distribution[&pop.get_religion()] += pop.get_size(); } } diff --git a/src/openvic-simulation/map/Province.hpp b/src/openvic-simulation/map/Province.hpp index 6c022b7..2fd15c9 100644 --- a/src/openvic-simulation/map/Province.hpp +++ b/src/openvic-simulation/map/Province.hpp @@ -3,6 +3,7 @@ #include #include "openvic-simulation/economy/Building.hpp" +#include "openvic-simulation/politics/Ideology.hpp" #include "openvic-simulation/pop/Pop.hpp" namespace OpenVic { @@ -72,7 +73,10 @@ namespace OpenVic { std::vector pops; Pop::pop_size_t total_population; - distribution_t pop_types, cultures, religions; + decimal_map_t PROPERTY(pop_type_distribution); + decimal_map_t PROPERTY(ideology_distribution); + decimal_map_t PROPERTY(culture_distribution); + decimal_map_t PROPERTY(religion_distribution); std::vector adjacencies; province_positions_t positions; @@ -109,9 +113,6 @@ namespace OpenVic { size_t get_pop_count() const; std::vector const& get_pops() const; Pop::pop_size_t get_total_population() const; - distribution_t const& get_pop_type_distribution() const; - distribution_t const& get_culture_distribution() const; - distribution_t const& get_religion_distribution() const; void update_pops(); void update_state(Date today); diff --git a/src/openvic-simulation/military/Deployment.cpp b/src/openvic-simulation/military/Deployment.cpp index b5d335a..0530986 100644 --- a/src/openvic-simulation/military/Deployment.cpp +++ b/src/openvic-simulation/military/Deployment.cpp @@ -37,9 +37,6 @@ bool DeploymentManager::add_deployment( Logger::error("Attemped to load order of battle with no path! Something is very wrong!"); return false; } - if (armies.empty() && navies.empty() && leaders.empty()) { - Logger::warning("Loading redundant empty order of battle at ", path); - } return deployments.add_item( std::make_unique(std::move(path), std::move(armies), std::move(navies), std::move(leaders)) @@ -47,8 +44,8 @@ bool DeploymentManager::add_deployment( } bool DeploymentManager::load_oob_file( - GameManager const& game_manager, Dataloader const& dataloader, std::string_view history_path, Deployment const*& deployment, - bool fail_on_missing + GameManager const& game_manager, Dataloader const& dataloader, std::string_view history_path, + Deployment const*& deployment, bool fail_on_missing ) { deployment = get_deployment_by_identifier(history_path); if (deployment != nullptr) { @@ -57,14 +54,13 @@ bool DeploymentManager::load_oob_file( if (missing_oob_files.contains(history_path)) { return !fail_on_missing; } - static const fs::path oob_directory = "history/units/"; - fs::path full_path = oob_directory; - full_path += history_path; - const fs::path lookedup_path = dataloader.lookup_file(full_path, false); + static constexpr std::string_view oob_directory = "history/units/"; + const fs::path lookedup_path = + dataloader.lookup_file(StringUtils::append_string_views(oob_directory, history_path), false); if (lookedup_path.empty()) { missing_oob_files.emplace(history_path); if (fail_on_missing) { - Logger::warning("Could not find OOB file ", full_path, "!"); + Logger::warning("Could not find OOB file ", history_path, "!"); return false; } else { return true; diff --git a/src/openvic-simulation/military/LeaderTrait.hpp b/src/openvic-simulation/military/LeaderTrait.hpp index e61a1fc..16b4201 100644 --- a/src/openvic-simulation/military/LeaderTrait.hpp +++ b/src/openvic-simulation/military/LeaderTrait.hpp @@ -46,7 +46,7 @@ namespace OpenVic { struct LeaderTraitManager { private: IdentifierRegistry leader_traits; - inline static const string_set_t allowed_modifiers = { + static inline const string_set_t allowed_modifiers = { "attack", "defence", "morale", "organisation", "reconnaissance", "speed", "attrition", "experience", "reliability" }; diff --git a/src/openvic-simulation/pop/Culture.cpp b/src/openvic-simulation/pop/Culture.cpp index 9159fe1..47501e0 100644 --- a/src/openvic-simulation/pop/Culture.cpp +++ b/src/openvic-simulation/pop/Culture.cpp @@ -174,7 +174,7 @@ bool CultureManager::load_culture_file(ast::NodeCPtr root) { return false; } - static const std::string default_unit_graphical_culture_type_identifier = "Generic"; + static constexpr std::string_view default_unit_graphical_culture_type_identifier = "Generic"; GraphicalCultureType const* const default_unit_graphical_culture_type = get_graphical_culture_type_by_identifier(default_unit_graphical_culture_type_identifier); if (default_unit_graphical_culture_type == nullptr) { diff --git a/src/openvic-simulation/types/IdentifierRegistry.hpp b/src/openvic-simulation/types/IdentifierRegistry.hpp index e68f2a4..734421d 100644 --- a/src/openvic-simulation/types/IdentifierRegistry.hpp +++ b/src/openvic-simulation/types/IdentifierRegistry.hpp @@ -10,6 +10,21 @@ #include "openvic-simulation/utility/Logger.hpp" namespace OpenVic { + + constexpr bool valid_basic_identifier_char(char c) { + return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '_'; + } + constexpr bool valid_basic_identifier(std::string_view identifier) { + return std::all_of(identifier.begin(), identifier.end(), valid_basic_identifier_char); + } + constexpr std::string_view extract_basic_identifier_prefix(std::string_view identifier) { + size_t len = 0; + while (len < identifier.size() && valid_basic_identifier_char(identifier[len])) { + ++len; + } + return { identifier.data(), len }; + } + /* * Base class for objects with a non-empty string identifier. Uniquely named instances of a type derived from this class * can be entered into an IdentifierRegistry instance. @@ -80,8 +95,6 @@ namespace OpenVic { } } - using distribution_t = decimal_map_t; - /* Callbacks for trying to add duplicate keys via UniqueKeyRegistry::add_item */ static bool duplicate_fail_callback(std::string_view registry_name, std::string_view duplicate_identifier) { Logger::error( @@ -192,20 +205,20 @@ namespace OpenVic { } } -#define GETTERS \ - value_type _const* get_item_by_identifier(std::string_view identifier) _const { \ +#define GETTERS(CONST) \ + value_type CONST* get_item_by_identifier(std::string_view identifier) CONST { \ const typename decltype(identifier_index_map)::const_iterator it = identifier_index_map.find(identifier); \ if (it != identifier_index_map.end()) { \ return GetPointer(items[it->second]); \ } \ return nullptr; \ } \ - value_type _const* get_item_by_index(size_t index) _const { \ + value_type CONST* get_item_by_index(size_t index) CONST { \ return index < items.size() ? &items[index] : nullptr; \ } \ - NodeTools::callback_t expect_item_str(NodeTools::callback_t callback) _const { \ + NodeTools::callback_t expect_item_str(NodeTools::callback_t callback) CONST { \ return [this, callback](std::string_view identifier) -> bool { \ - value_type _const* item = get_item_by_identifier(identifier); \ + value_type CONST* item = get_item_by_identifier(identifier); \ if (item != nullptr) { \ return callback(*item); \ } \ @@ -213,22 +226,19 @@ namespace OpenVic { return false; \ }; \ } \ - NodeTools::node_callback_t expect_item_identifier(NodeTools::callback_t callback) _const { \ + NodeTools::node_callback_t expect_item_identifier(NodeTools::callback_t callback) CONST { \ return NodeTools::expect_identifier(expect_item_str(callback)); \ } \ - NodeTools::node_callback_t expect_item_dictionary(NodeTools::callback_t callback) \ - _const { \ + NodeTools::node_callback_t expect_item_dictionary( \ + NodeTools::callback_t callback \ + ) CONST { \ return NodeTools::expect_dictionary([this, callback](std::string_view key, ast::NodeCPtr value) -> bool { \ return expect_item_str(std::bind(callback, std::placeholders::_1, value))(key); \ }); \ } -#define _const - GETTERS -#undef _const -#define _const const - GETTERS -#undef _const + GETTERS() + GETTERS(const) #undef GETTERS diff --git a/src/openvic-simulation/utility/Logger.cpp b/src/openvic-simulation/utility/Logger.cpp deleted file mode 100644 index 63dfd6c..0000000 --- a/src/openvic-simulation/utility/Logger.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "Logger.hpp" - -#include - -using namespace OpenVic; - -void Logger::set_logger_funcs() { - Logger::set_info_func([](std::string&& str) { - std::cout << "[INFO] " << str; - }); - Logger::set_warning_func([](std::string&& str) { - std::cerr << "[WARNING] " << str; - }); - Logger::set_error_func([](std::string&& str) { - std::cerr << "[ERROR] " << str; - }); -} - -char const* Logger::get_filename(char const* filepath, char const* default_path) { - if (filepath == nullptr) { - return default_path; - } - char const* last_slash = filepath; - while (*filepath != '\0') { - if (*filepath == '\\' || *filepath == '/') { - last_slash = filepath + 1; - } - filepath++; - } - if (*last_slash == '\0') { - return default_path; - } - return last_slash; -} diff --git a/src/openvic-simulation/utility/Logger.hpp b/src/openvic-simulation/utility/Logger.hpp index 04306f7..20c7fdd 100644 --- a/src/openvic-simulation/utility/Logger.hpp +++ b/src/openvic-simulation/utility/Logger.hpp @@ -1,12 +1,16 @@ #pragma once #include +#include #include #include + #ifdef __cpp_lib_source_location #include #endif +#include "openvic-simulation/utility/StringUtils.hpp" + namespace OpenVic { #ifndef __cpp_lib_source_location @@ -52,8 +56,17 @@ namespace OpenVic { #endif public: - static void set_logger_funcs(); - static char const* get_filename(char const* filepath, char const* default_path = nullptr); + static void set_logger_funcs() { + set_info_func([](std::string&& str) { + std::cout << "[INFO] " << str; + }); + set_warning_func([](std::string&& str) { + std::cerr << "[WARNING] " << str; + }); + set_error_func([](std::string&& str) { + std::cerr << "[ERROR] " << str; + }); + } private: struct log_channel_t { @@ -65,13 +78,13 @@ namespace OpenVic { struct log { log(log_channel_t& log_channel, Ts&&... ts, source_location const& location) { std::stringstream stream; - stream << get_filename(location.file_name()) << "(" + stream << StringUtils::get_filename(location.file_name()) << "(" /* Function name removed to reduce clutter. It is already included * in Godot's print functions, so this was repeating it. */ //<< location.line() << ") `" << location.function_name() << "`: "; << location.line() << "): "; ((stream << std::forward(ts)), ...); - stream << "\n" << std::endl; + stream << std::endl; log_channel.queue.push(stream.str()); if (log_channel.func) { do { diff --git a/src/openvic-simulation/utility/StringUtils.hpp b/src/openvic-simulation/utility/StringUtils.hpp index d968bf6..ede1d6b 100644 --- a/src/openvic-simulation/utility/StringUtils.hpp +++ b/src/openvic-simulation/utility/StringUtils.hpp @@ -1,4 +1,7 @@ +#pragma once + #include +#include #include #include @@ -90,7 +93,7 @@ namespace OpenVic::StringUtils { return result; } - constexpr uint64_t string_to_uint64(char const* str, size_t length, bool* successful = nullptr, int base = 10) { + inline constexpr uint64_t string_to_uint64(char const* str, size_t length, bool* successful = nullptr, int base = 10) { return string_to_uint64(str, str + length, successful, base); } @@ -135,11 +138,72 @@ namespace OpenVic::StringUtils { } } - constexpr int64_t string_to_int64(char const* str, size_t length, bool* successful = nullptr, int base = 10) { + inline constexpr int64_t string_to_int64(char const* str, size_t length, bool* successful = nullptr, int base = 10) { return string_to_int64(str, str + length, successful, base); } inline int64_t string_to_int64(std::string_view str, bool* successful = nullptr, int base = 10) { return string_to_int64(str.data(), str.length(), successful, base); } + + inline constexpr std::string_view get_filename(std::string_view path) { + size_t pos = path.size(); + while (pos > 0 && path[pos - 1] != '/' && path[pos - 1] != '\\') { + --pos; + } + path.remove_prefix(pos); + return path; + } + + inline constexpr char const* get_filename(char const* filepath, char const* default_path = nullptr) { + const std::string_view filename { get_filename(std::string_view { filepath }) }; + if (!filename.empty()) { + return filename.data(); + } + return default_path; + } + + inline std::string make_forward_slash_path(std::string_view path) { + std::string ret { path }; + std::replace(ret.begin(), ret.end(), '\\', '/'); + for (char& c : ret) { + if (c == '\\') { + c = '/'; + } + } + return ret; + } + + inline constexpr std::string_view remove_leading_slashes(std::string_view path) { + size_t count = 0; + while (count < path.size() && (path[count] == '/' || path[count] == '\\')) { + ++count; + } + path.remove_prefix(count); + return path; + } + + template + requires (std::is_same_v && ...) + inline std::string _append_string_views(Args... args) { + std::string ret; + ret.reserve((args.size() + ...)); + (ret.append(args), ...); + return ret; + } + + template + requires (std::is_convertible_v && ...) + inline std::string append_string_views(Args... args) { + return _append_string_views(std::string_view { args }...); + } + + inline constexpr std::string_view remove_extension(std::string_view path) { + size_t pos = path.size(); + while (pos > 0 && path[--pos] != '.') {} + if (path[pos] == '.') { + path.remove_suffix(path.size() - pos); + } + return path; + } } -- cgit v1.2.3-56-ga3b1