diff options
author | Hop311 <Hop3114@gmail.com> | 2023-09-09 23:49:54 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-09 23:49:54 +0200 |
commit | 6278a35f4704574933464700026d8deb997da5c1 (patch) | |
tree | eb36a9b030b263d825eb93638e64deb0dbd38a78 /src | |
parent | bec619fc8f554cb075fcef2428f3b6bdb5e88e82 (diff) | |
parent | 3d7fbd9b376811ca0ed226fa78bcc8b6279ba8dc (diff) |
Merge pull request #14 from OpenVicProject/dataloading
Dataloading scaffolding + basic culture and pop history loading
Diffstat (limited to 'src')
49 files changed, 3164 insertions, 984 deletions
diff --git a/src/headless/main.cpp b/src/headless/main.cpp index 70a57a9..3ae08f0 100644 --- a/src/headless/main.cpp +++ b/src/headless/main.cpp @@ -1,22 +1,76 @@ -#ifdef OPENVIC_HEADLESS_SIM -#include <iostream> -#include <string> -#include "openvic/Simulation.hpp" -#include "openvic/dataloader/Dataloader.hpp" +#include <openvic-simulation/GameManager.hpp> +#include <openvic-simulation/dataloader/Dataloader.hpp> +#include <openvic-simulation/utility/Logger.hpp> +using namespace OpenVic; -int main() { - std::cout << "HEADLESS SIMULATION" << std::endl; +static char const* get_program_name(char const* name) { + static constexpr char const* missing_name = "<program>"; + if (name == nullptr) return missing_name; + char const* last_separator = name; + while (*name != '\0') { + if (*name == '/' || *name == '\\') { + last_separator = name + 1; + } + ++name; + } + if (*last_separator == '\0') return missing_name; + return last_separator; +} + +static bool headless_load(Dataloader::path_vector_t const& roots) { + bool ret = true; + + Logger::set_info_func([](std::string&& str) { std::cout << str; }); + Logger::set_warning_func([](std::string&& str) { std::cerr << str; }); + Logger::set_error_func([](std::string&& str) { std::cerr << str; }); + GameManager game_manager { []() { + Logger::info("State updated"); + } }; + Dataloader dataloader; - std::string vic2FolderLocation = ""; - if (vic2FolderLocation.length() <= 0) { - std::cout << "Path to Victoria 2 folder not specified. Manually specify location: "; - std::cin >> vic2FolderLocation; + if (!dataloader.set_roots(roots)) { + Logger::error("Failed to set dataloader roots!"); + ret = false; } - std::filesystem::path path(vic2FolderLocation); + if (!dataloader.load_defines(game_manager)) { + Logger::error("Failed to load defines!"); + ret = false; + } + if (!game_manager.load_hardcoded_defines()) { + Logger::error("Failed to load hardcoded defines!"); + ret = false; + } + + return ret; +} + +int main(int argc, char const* argv[]) { + Dataloader::path_vector_t roots; + if (argc < 2) { + // TODO - non-Windows default paths + static const fs::path default_path = "C:/Program Files (x86)/Steam/steamapps/common/Victoria 2"; + + std::cout + << "Usage: " << get_program_name(argc > 0 ? argv[0] : nullptr) << " <base defines dir> [[mod defines dir]+]\n" + << "Requires defines path(s) as arguments, starting with the base defines and continuing with mods.\n" + << "(Paths with spaces need to be enclosed in \"quotes\").\n" + << "Defaulting to " << default_path << std::endl; + roots.push_back(default_path); + } else { + for (int i = 1; i < argc; ++i) { + roots.push_back(argv[i]); + } + } + + std::cout << "!!! HEADLESS SIMULATION START !!!" << std::endl; + + const bool ret = headless_load(roots); + + std::cout << "!!! HEADLESS SIMULATION END !!!" << std::endl; + + std::cout << "\nLoad returned: " << (ret ? "SUCCESS" : "FAILURE") << std::endl; - OpenVic::Simulation sim; - std::cout << (OpenVic::Dataloader::loadDir(path, sim) ? "Dataloader suceeded" : "Dataloader failed") << std::endl; + return ret ? 0 : -1; } -#endif
\ No newline at end of file diff --git a/src/openvic/GameAdvancementHook.cpp b/src/openvic-simulation/GameAdvancementHook.cpp index ac16158..ac16158 100644 --- a/src/openvic/GameAdvancementHook.cpp +++ b/src/openvic-simulation/GameAdvancementHook.cpp diff --git a/src/openvic/GameAdvancementHook.hpp b/src/openvic-simulation/GameAdvancementHook.hpp index 59e43a4..59e43a4 100644 --- a/src/openvic/GameAdvancementHook.hpp +++ b/src/openvic-simulation/GameAdvancementHook.hpp diff --git a/src/openvic-simulation/GameManager.cpp b/src/openvic-simulation/GameManager.cpp new file mode 100644 index 0000000..0b42230 --- /dev/null +++ b/src/openvic-simulation/GameManager.cpp @@ -0,0 +1,131 @@ +#include "GameManager.hpp" + +#include "openvic-simulation/utility/Logger.hpp" + +using namespace OpenVic; + +GameManager::GameManager(state_updated_func_t state_updated_callback) + : clock { [this]() { tick(); }, [this]() { update_state(); } }, + state_updated { state_updated_callback } {} + +void GameManager::set_needs_update() { + needs_update = true; +} + +void GameManager::update_state() { + if (!needs_update) return; + Logger::info("Update: ", today); + map.update_state(today); + if (state_updated) state_updated(); + needs_update = false; +} + +/* REQUIREMENTS: + * SS-98, SS-101 + */ +void GameManager::tick() { + today++; + Logger::info("Tick: ", today); + map.tick(today); + set_needs_update(); +} + +bool GameManager::setup() { + session_start = time(nullptr); + clock.reset(); + today = { 1836 }; + good_manager.reset_to_defaults(); + bool ret = map.setup(good_manager, building_manager, pop_manager); + set_needs_update(); + return ret; +} + +Date const& GameManager::get_today() const { + return today; +} + +bool GameManager::expand_building(Province::index_t province_index, const std::string_view building_type_identifier) { + set_needs_update(); + Province* province = map.get_province_by_index(province_index); + if (province == nullptr) { + Logger::error("Invalid province index ", province_index, " while trying to expand building ", building_type_identifier); + return false; + } + return province->expand_building(building_type_identifier); +} + +bool GameManager::load_hardcoded_defines() { + bool ret = true; + + static constexpr colour_t LOW_ALPHA_VALUE = float_to_alpha_value(0.4f); + static constexpr colour_t HIGH_ALPHA_VALUE = float_to_alpha_value(0.7f); + using mapmode_t = std::pair<std::string, Mapmode::colour_func_t>; + const std::vector<mapmode_t> mapmodes { + { "mapmode_terrain", + [](Map const&, Province const& province) -> colour_t { + return LOW_ALPHA_VALUE | (province.is_water() ? 0x4287F5 : 0x0D7017); + } }, + { "mapmode_province", + [](Map const&, Province const& province) -> colour_t { + return HIGH_ALPHA_VALUE | province.get_colour(); + } }, + { "mapmode_region", + [](Map const&, Province const& province) -> colour_t { + Region const* region = province.get_region(); + if (region != nullptr) return HIGH_ALPHA_VALUE | region->get_colour(); + return NULL_COLOUR; + } }, + { "mapmode_index", + [](Map const& map, Province const& province) -> colour_t { + const colour_t f = fraction_to_colour_byte(province.get_index(), map.get_province_count() + 1); + return HIGH_ALPHA_VALUE | (f << 16) | (f << 8) | f; + } }, + { "mapmode_rgo", + [](Map const& map, Province const& province) -> colour_t { + Good const* rgo = province.get_rgo(); + if (rgo != nullptr) return HIGH_ALPHA_VALUE | rgo->get_colour(); + return NULL_COLOUR; + } }, + { "mapmode_infrastructure", + [](Map const& map, Province const& province) -> colour_t { + Building const* railroad = province.get_building_by_identifier("building_railroad"); + if (railroad != nullptr) { + colour_t val = fraction_to_colour_byte(railroad->get_level(), railroad->get_type().get_max_level() + 1, 0.5f, 1.0f); + switch (railroad->get_expansion_state()) { + case Building::ExpansionState::CannotExpand: val <<= 16; break; + case Building::ExpansionState::CanExpand: break; + default: val <<= 8; break; + } + return HIGH_ALPHA_VALUE | val; + } + return NULL_COLOUR; + } }, + { "mapmode_population", + [](Map const& map, Province const& province) -> colour_t { + return HIGH_ALPHA_VALUE | (fraction_to_colour_byte(province.get_total_population(), map.get_highest_province_population() + 1, 0.1f, 1.0f) << 8); + } }, + { "mapmode_culture", + [](Map const& map, Province const& province) -> colour_t { + HasIdentifierAndColour const* largest = get_largest_item(province.get_culture_distribution()).first; + return largest != nullptr ? HIGH_ALPHA_VALUE | largest->get_colour() : NULL_COLOUR; + } }, + { "mapmode_religion", + [](Map const& map, Province const& province) -> colour_t { + HasIdentifierAndColour const* largest = get_largest_item(province.get_religion_distribution()).first; + return largest != nullptr ? HIGH_ALPHA_VALUE | largest->get_colour() : NULL_COLOUR; + } } + }; + for (mapmode_t const& mapmode : mapmodes) + ret &= map.add_mapmode(mapmode.first, mapmode.second); + map.lock_mapmodes(); + + using building_type_t = std::tuple<std::string, Building::level_t, Timespan>; + const std::vector<building_type_t> building_types { + { "building_fort", 4, 8 }, { "building_naval_base", 6, 15 }, { "building_railroad", 5, 10 } + }; + for (building_type_t const& type : building_types) + ret &= building_manager.add_building_type(std::get<0>(type), std::get<1>(type), std::get<2>(type)); + building_manager.lock_building_types(); + + return ret; +} diff --git a/src/openvic/GameManager.hpp b/src/openvic-simulation/GameManager.hpp index 52de06e..d221a99 100644 --- a/src/openvic/GameManager.hpp +++ b/src/openvic-simulation/GameManager.hpp @@ -1,8 +1,8 @@ #pragma once -#include "GameAdvancementHook.hpp" -#include "economy/Good.hpp" -#include "map/Map.hpp" +#include "openvic-simulation/GameAdvancementHook.hpp" +#include "openvic-simulation/economy/Good.hpp" +#include "openvic-simulation/map/Map.hpp" namespace OpenVic { struct GameManager { @@ -27,9 +27,14 @@ namespace OpenVic { public: GameManager(state_updated_func_t state_updated_callback); - return_t setup(); + bool setup(); Date const& get_today() const; - return_t expand_building(index_t province_index, const std::string_view building_type_identifier); + bool expand_building(Province::index_t province_index, const std::string_view building_type_identifier); + + /* Hardcoded data for defining things for which parsing from files has + * not been implemented, currently mapmodes and building types. + */ + bool load_hardcoded_defines(); }; } diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp new file mode 100644 index 0000000..82957fc --- /dev/null +++ b/src/openvic-simulation/dataloader/Dataloader.cpp @@ -0,0 +1,286 @@ +#include "Dataloader.hpp" + +#include <openvic-dataloader/csv/Parser.hpp> +#include <openvic-dataloader/detail/CallbackOStream.hpp> +#include <openvic-dataloader/v2script/Parser.hpp> + +#include "openvic-simulation/GameManager.hpp" +#include "openvic-simulation/utility/Logger.hpp" + +using namespace OpenVic; +using namespace OpenVic::NodeTools; +using namespace ovdl; + +bool Dataloader::set_roots(path_vector_t new_roots) { + if (!roots.empty()) { + Logger::error("Overriding existing dataloader roots!"); + roots.clear(); + } + bool ret = true; + for (std::reverse_iterator<path_vector_t::const_iterator> it = new_roots.crbegin(); it != new_roots.crend(); ++it) { + if (std::find(roots.begin(), roots.end(), *it) == roots.end()) { + if (fs::is_directory(*it)) { + Logger::info("Adding dataloader root: ", *it); + roots.push_back(*it); + } else { + Logger::error("Invalid dataloader root (must be an existing directory): ", *it); + ret = false; + } + } else { + Logger::error("Duplicate dataloader root: ", *it); + ret = false; + } + } + if (roots.empty()) { + Logger::error("Dataloader has no roots after attempting to add ", new_roots.size()); + ret = false; + } + return ret; +} + +fs::path Dataloader::lookup_file(fs::path const& path) const { + for (fs::path const& root : roots) { + const fs::path composed = root / path; + if (fs::is_regular_file(composed)) { + return composed; + } + } + Logger::error("Lookup for ", path, " failed!"); + return {}; +} + +const fs::path Dataloader::TXT = ".txt"; + +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 { + + path_vector_t ret; + for (fs::path const& root : roots) { + const fs::path composed = root / path; + std::error_code ec; + for (fs::directory_entry const& entry : fs::directory_iterator { composed, ec }) { + if (entry.is_regular_file()) { + const fs::path file = entry; + if (extension == nullptr || file.extension() == *extension) { + if (!contains_file_with_name(ret, file.filename())) { + ret.push_back(file); + } + } + } + } + } + return ret; +} + +bool Dataloader::apply_to_files_in_dir(fs::path const& path, std::function<bool(fs::path const&)> callback, fs::path const* extension) const { + + bool ret = true; + for (fs::path const& file : lookup_files_in_dir(path, extension)) { + ret &= callback(file); + } + return ret; +} + +template<std::derived_from<detail::BasicParser> Parser, bool (Parser::*parse_func)()> +static Parser _run_ovdl_parser(fs::path const& path) { + Parser parser; + std::string buffer; + auto error_log_stream = ovdl::detail::CallbackStream { + [](void const* s, std::streamsize n, void* user_data) -> std::streamsize { + if (s != nullptr && n > 0 && user_data != nullptr) { + static_cast<std::string*>(user_data)->append(static_cast<char const*>(s), n); + return n; + } else { + Logger::error("Invalid input to parser error log callback: ", s, " / ", n, " / ", user_data); + return 0; + } + }, + &buffer + }; + parser.set_error_log_to(error_log_stream); + parser.load_from_file(path); + if (!buffer.empty()) { + Logger::error("Parser load errors:\n\n", buffer, "\n"); + buffer.clear(); + } + if (parser.has_fatal_error() || parser.has_error()) { + Logger::error("Parser errors while loading ", path); + return parser; + } + if (!(parser.*parse_func)()) { + Logger::error("Parse function returned false!"); + } + if (!buffer.empty()) { + Logger::error("Parser parse errors:\n\n", buffer, "\n"); + buffer.clear(); + } + if (parser.has_fatal_error() || parser.has_error()) { + Logger::error("Parser errors while parsing ", path); + } + return parser; +} + +static v2script::Parser _parse_defines(fs::path const& path) { + return _run_ovdl_parser<v2script::Parser, &v2script::Parser::simple_parse>(path); +} + +static csv::Windows1252Parser _parse_csv(fs::path const& path) { + return _run_ovdl_parser<csv::Windows1252Parser, &csv::Windows1252Parser::parse_csv>(path); +} + +bool Dataloader::_load_pop_types(PopManager& pop_manager, fs::path const& pop_type_directory) const { + const bool ret = apply_to_files_in_dir(pop_type_directory, + [&pop_manager](fs::path const& file) -> bool { + return pop_manager.load_pop_type_file(file.stem().string(), _parse_defines(file).get_file_node()); + } + ); + if (!ret) { + Logger::error("Failed to load pop types!"); + } + pop_manager.lock_pop_types(); + return ret; +} + +bool Dataloader::_load_map_dir(Map& map, fs::path const& map_directory) const { + 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)); + + std::vector<std::string_view> water_province_identifiers; + +#define APPLY_TO_MAP_PATHS(F) \ + F(definitions) F(provinces) F(positions) F(terrain) F(rivers) \ + F(terrain_definition) F(tree_definition) F(continent) F(adjacencies) \ + F(region) F(region_sea) F(province_flag_sprite) + +#define MAP_PATH_VAR(X) std::string_view X = default_##X; + APPLY_TO_MAP_PATHS(MAP_PATH_VAR) +#undef MAP_PATH_VAR + + bool ret = expect_dictionary_keys( + "max_provinces", ONE_EXACTLY, + expect_uint( + [&map](uint64_t val) -> bool { + if (Province::NULL_INDEX < val && val <= Province::MAX_INDEX) { + return map.set_max_provinces(val); + } + Logger::error("Invalid max province count ", val, " (out of valid range ", + Province::NULL_INDEX, " < max_provinces <= ", Province::MAX_INDEX, ")"); + return false; + } + ), + "sea_starts", ONE_EXACTLY, + expect_list_reserve_length( + water_province_identifiers, + expect_identifier( + [&water_province_identifiers](std::string_view identifier) -> bool { + water_province_identifiers.push_back(identifier); + return true; + } + ) + ), + +#define MAP_PATH_DICT_ENTRY(X) \ + #X, ONE_EXACTLY, expect_string(assign_variable_callback(X)), + APPLY_TO_MAP_PATHS(MAP_PATH_DICT_ENTRY) +#undef MAP_PATH_DICT_ENTRY + +#undef APPLY_TO_MAP_PATHS + + "border_heights", ZERO_OR_ONE, success_callback, + "terrain_sheet_heights", ZERO_OR_ONE, success_callback, + "tree", ZERO_OR_ONE, success_callback, + "border_cutoff", ZERO_OR_ONE, success_callback + )(parser.get_file_node()); + + if (!ret) { + Logger::error("Failed to load map default file!"); + } + + if (!map.load_province_definitions(_parse_csv(lookup_file(map_directory / definitions)).get_lines())) { + Logger::error("Failed to load province definitions file!"); + ret = false; + } + + if (!map.set_water_province_list(water_province_identifiers)) { + Logger::error("Failed to set water provinces!"); + ret = false; + } + map.lock_water_provinces(); + + return ret; +} + +bool Dataloader::load_defines(GameManager& game_manager) const { + static const fs::path good_file = "common/goods.txt"; + static const fs::path pop_type_directory = "poptypes"; + static const fs::path graphical_culture_type_file = "common/graphicalculturetype.txt"; + static const fs::path culture_file = "common/cultures.txt"; + static const fs::path religion_file = "common/religion.txt"; + static const fs::path map_directory = "map"; + + bool ret = true; + + if (!game_manager.good_manager.load_good_file(_parse_defines(lookup_file(good_file)).get_file_node())) { + Logger::error("Failed to load goods!"); + ret = false; + } + if (!_load_pop_types(game_manager.pop_manager, pop_type_directory)) { + Logger::error("Failed to load pop types!"); + ret = false; + } + if (!game_manager.pop_manager.culture_manager.load_graphical_culture_type_file(_parse_defines(lookup_file(graphical_culture_type_file)).get_file_node())) { + Logger::error("Failed to load graphical culture types!"); + ret = false; + } + if (!game_manager.pop_manager.culture_manager.load_culture_file(_parse_defines(lookup_file(culture_file)).get_file_node())) { + Logger::error("Failed to load cultures!"); + ret = false; + } + if (!game_manager.pop_manager.religion_manager.load_religion_file(_parse_defines(lookup_file(religion_file)).get_file_node())) { + Logger::error("Failed to load religions!"); + ret = false; + } + if (!_load_map_dir(game_manager.map, map_directory)) { + Logger::error("Failed to load map!"); + ret = false; + } + + return ret; +} + +bool Dataloader::load_pop_history(GameManager& game_manager, fs::path const& path) const { + return apply_to_files_in_dir(path, + [&game_manager](fs::path const& file) -> bool { + return expect_dictionary( + [&game_manager](std::string_view province_key, ast::NodeCPtr province_node) -> bool { + Province* province = game_manager.map.get_province_by_identifier(province_key); + if (province == nullptr) { + Logger::error("Invalid province id: ", province_key); + return false; + } + return province->load_pop_list(game_manager.pop_manager, province_node); + } + )(_parse_defines(file).get_file_node()); + } + ); +} diff --git a/src/openvic-simulation/dataloader/Dataloader.hpp b/src/openvic-simulation/dataloader/Dataloader.hpp new file mode 100644 index 0000000..efada89 --- /dev/null +++ b/src/openvic-simulation/dataloader/Dataloader.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include <filesystem> +#include <functional> +#include <vector> + +namespace OpenVic { + namespace fs = std::filesystem; + + struct GameManager; + struct PopManager; + struct Map; + + class Dataloader { + public: + using path_vector_t = std::vector<fs::path>; + + private: + path_vector_t roots; + + bool _load_pop_types(PopManager& pop_manager, fs::path const& pop_type_directory) const; + bool _load_map_dir(Map& map, fs::path const& map_directory) const; + + public: + Dataloader() = default; + + /* In reverse-load order, so base defines first and final loaded mod last */ + bool set_roots(path_vector_t new_roots); + + fs::path lookup_file(fs::path const& path) const; + static const fs::path TXT; + path_vector_t lookup_files_in_dir(fs::path const& path, fs::path const* extension = &TXT) const; + bool apply_to_files_in_dir(fs::path const& path, std::function<bool(fs::path const&)> callback, + fs::path const* extension = &TXT) const; + + bool load_defines(GameManager& game_manager) const; + bool load_pop_history(GameManager& game_manager, fs::path const& path) const; + }; +} diff --git a/src/openvic-simulation/dataloader/NodeTools.cpp b/src/openvic-simulation/dataloader/NodeTools.cpp new file mode 100644 index 0000000..63a97ad --- /dev/null +++ b/src/openvic-simulation/dataloader/NodeTools.cpp @@ -0,0 +1,254 @@ +#include "NodeTools.hpp" + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +template<typename T> +static node_callback_t _expect_type(callback_t<T const&> callback) { + return [callback](ast::NodeCPtr node) -> bool { + if (node != nullptr) { + T const* cast_node = node->cast_to<T>(); + if (cast_node != nullptr) { + return callback(*cast_node); + } + Logger::error("Invalid node type ", node->get_type(), " when expecting ", T::get_type_static()); + } else { + Logger::error("Null node when expecting ", T::get_type_static()); + } + return false; + }; +} + +template<typename T = ast::AbstractStringNode> +requires(std::derived_from<T, ast::AbstractStringNode>) +static callback_t<T const&> abstract_string_node_callback(callback_t<std::string_view> callback) { + return [callback](T const& node) -> bool { + return callback(node._name); + }; +} + +node_callback_t NodeTools::expect_identifier(callback_t<std::string_view> callback) { + return _expect_type<ast::IdentifierNode>(abstract_string_node_callback<ast::IdentifierNode>(callback)); +} + +node_callback_t NodeTools::expect_string(callback_t<std::string_view> callback) { + return _expect_type<ast::StringNode>(abstract_string_node_callback<ast::StringNode>(callback)); +} + +node_callback_t NodeTools::expect_identifier_or_string(callback_t<std::string_view> callback) { + return [callback](ast::NodeCPtr node) -> bool { + if (node != nullptr) { + ast::AbstractStringNode const* cast_node = node->cast_to<ast::IdentifierNode>(); + if (cast_node == nullptr) { + cast_node = node->cast_to<ast::StringNode>(); + } + if (cast_node != nullptr) { + return abstract_string_node_callback(callback)(*cast_node); + } + Logger::error("Invalid node type ", node->get_type(), " when expecting ", ast::IdentifierNode::get_type_static(), " or ", ast::StringNode::get_type_static()); + } else { + Logger::error("Null node when expecting ", ast::IdentifierNode::get_type_static(), " or ", ast::StringNode::get_type_static()); + } + return false; + }; +} + +node_callback_t NodeTools::expect_bool(callback_t<bool> callback) { + return expect_identifier( + [callback](std::string_view identifier) -> bool { + if (identifier == "yes") { + return callback(true); + } else if (identifier == "no") { + return callback(false); + } + Logger::error("Invalid bool identifier text: ", identifier); + return false; + } + ); +} + +node_callback_t NodeTools::expect_int(callback_t<int64_t> callback) { + return expect_identifier( + [callback](std::string_view identifier) -> bool { + bool successful = false; + const int64_t val = StringUtils::string_to_int64(identifier, &successful, 10); + if (successful) { + return callback(val); + } + Logger::error("Invalid int identifier text: ", identifier); + return false; + } + ); +} + +node_callback_t NodeTools::expect_uint(callback_t<uint64_t> callback) { + return expect_identifier( + [callback](std::string_view identifier) -> bool { + bool successful = false; + const uint64_t val = StringUtils::string_to_uint64(identifier, &successful, 10); + if (successful) { + return callback(val); + } + Logger::error("Invalid uint identifier text: ", identifier); + return false; + } + ); +} + +node_callback_t NodeTools::expect_fixed_point(callback_t<fixed_point_t> callback) { + return expect_identifier( + [callback](std::string_view identifier) -> bool { + bool successful = false; + const fixed_point_t val = fixed_point_t::parse(identifier.data(), identifier.length(), &successful); + if (successful) { + return callback(val); + } + Logger::error("Invalid fixed point identifier text: ", identifier); + return false; + } + ); +} + +node_callback_t NodeTools::expect_colour(callback_t<colour_t> callback) { + return [callback](ast::NodeCPtr node) -> bool { + colour_t col = NULL_COLOUR; + uint32_t components = 0; + bool ret = expect_list_of_length(3, + expect_fixed_point( + [&col, &components](fixed_point_t val) -> bool { + components++; + col <<= 8; + if (val < 0 || val > 255) { + Logger::error("Invalid colour component: ", val); + return false; + } else { + if (val <= 1) val *= 255; + col |= val.to_int32_t(); + return true; + } + } + ) + )(node); + ret &= callback(col << 8 * (3 - components)); + return ret; + }; +} + +node_callback_t NodeTools::expect_date(callback_t<Date> callback) { + return expect_identifier( + [callback](std::string_view identifier) -> bool { + bool successful = false; + const Date date = Date::from_string(identifier, &successful); + if (successful) { + return callback(date); + } + Logger::error("Invalid date identifier text: ", identifier); + return false; + } + ); +} + +node_callback_t NodeTools::expect_assign(key_value_callback_t callback) { + return _expect_type<ast::AssignNode>( + [callback](ast::AssignNode const& assign_node) -> bool { + return callback(assign_node._name, assign_node._initializer.get()); + } + ); +} + +node_callback_t NodeTools::expect_list_and_length(length_callback_t length_callback, node_callback_t callback) { + return _expect_type<ast::AbstractListNode>( + [length_callback, callback](ast::AbstractListNode const& list_node) -> bool { + std::vector<ast::NodeUPtr> const& list = list_node._statements; + bool ret = true; + size_t size = length_callback(list.size()); + if (size > list.size()) { + Logger::error("Trying to read more values than the list contains: ", size, " > ", list.size()); + size = list.size(); + ret = false; + } + std::for_each(list.begin(), list.begin() + size, + [callback, &ret](ast::NodeUPtr const& sub_node) -> void { + ret &= callback(sub_node.get()); + } + ); + return ret; + } + ); +} + +node_callback_t NodeTools::expect_list_of_length(size_t length, node_callback_t callback) { + return [length, callback](ast::NodeCPtr node) -> bool { + bool ret = true; + ret &= expect_list_and_length( + [length, &ret](size_t size) -> size_t { + if (size != length) { + Logger::error("List length ", size, " does not match expected length ", length); + ret = false; + if (length < size) return length; + } + return size; + }, + callback + )(node); + return ret; + }; +} + +node_callback_t NodeTools::expect_list(node_callback_t callback) { + return expect_list_and_length(default_length_callback, callback); +} + +node_callback_t NodeTools::expect_dictionary_and_length(length_callback_t length_callback, key_value_callback_t callback) { + return expect_list_and_length(length_callback, expect_assign(callback)); +} + +node_callback_t NodeTools::expect_dictionary(key_value_callback_t callback) { + return expect_dictionary_and_length(default_length_callback, callback); +} + +node_callback_t NodeTools::_expect_dictionary_keys_and_length(length_callback_t length_callback, bool allow_other_keys, key_map_t&& key_map) { + return [length_callback, allow_other_keys, key_map = std::move(key_map)](ast::NodeCPtr node) mutable -> bool { + bool ret = expect_dictionary_and_length( + length_callback, + [&key_map, allow_other_keys](std::string_view key, ast::NodeCPtr value) -> bool { + const key_map_t::iterator it = key_map.find(key); + if (it == key_map.end()) { + if (allow_other_keys) return true; + Logger::error("Invalid dictionary key: ", key); + return false; + } + dictionary_entry_t& entry = it->second; + if (++entry.count > 1 && !entry.can_repeat()) { + Logger::error("Invalid repeat of dictionary key: ", key); + return false; + } + return entry.callback(value); + } + )(node); + for (key_map_t::value_type const& key_entry : key_map) { + dictionary_entry_t const& entry = key_entry.second; + if (entry.must_appear() && entry.count < 1) { + Logger::error("Mandatory dictionary key not present: ", key_entry.first); + ret = false; + } + } + return ret; + }; +} + +node_callback_t NodeTools::name_list_callback(std::vector<std::string>& list) { + return expect_list_reserve_length( + list, + expect_identifier_or_string( + [&list](std::string_view str) -> bool { + if (!str.empty()) { + list.push_back(std::string { str }); + return true; + } + Logger::error("Empty identifier or string"); + return false; + } + ) + ); +} diff --git a/src/openvic-simulation/dataloader/NodeTools.hpp b/src/openvic-simulation/dataloader/NodeTools.hpp new file mode 100644 index 0000000..a68e922 --- /dev/null +++ b/src/openvic-simulation/dataloader/NodeTools.hpp @@ -0,0 +1,164 @@ +#pragma once + +#include <map> + +#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp> + +#include "openvic-simulation/types/Colour.hpp" +#include "openvic-simulation/types/Date.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" + +namespace OpenVic { + namespace ast = ovdl::v2script::ast; + + namespace NodeTools { + + template<typename... Args> + using callback_t = std::function<bool(Args...)>; + + using node_callback_t = callback_t<ast::NodeCPtr>; + constexpr bool success_callback(ast::NodeCPtr) { return true; } + + using key_value_callback_t = callback_t<std::string_view, ast::NodeCPtr>; + + node_callback_t expect_identifier(callback_t<std::string_view> callback); + node_callback_t expect_string(callback_t<std::string_view> callback); + node_callback_t expect_identifier_or_string(callback_t<std::string_view> callback); + node_callback_t expect_bool(callback_t<bool> callback); + node_callback_t expect_int(callback_t<int64_t> callback); + node_callback_t expect_uint(callback_t<uint64_t> callback); + node_callback_t expect_fixed_point(callback_t<fixed_point_t> callback); + node_callback_t expect_colour(callback_t<colour_t> callback); + node_callback_t expect_date(callback_t<Date> callback); + node_callback_t expect_assign(key_value_callback_t callback); + + using length_callback_t = std::function<size_t(size_t)>; + constexpr size_t default_length_callback(size_t size) { return size; }; + + node_callback_t expect_list_and_length(length_callback_t length_callback, node_callback_t callback); + node_callback_t expect_list_of_length(size_t length, node_callback_t callback); + node_callback_t expect_list(node_callback_t callback); + + node_callback_t expect_dictionary_and_length(length_callback_t length_callback, key_value_callback_t callback); + node_callback_t expect_dictionary(key_value_callback_t callback); + + struct dictionary_entry_t { + const enum class expected_count_t : uint8_t { + _MUST_APPEAR = 0b01, + _CAN_REPEAT = 0b10, + + ZERO_OR_ONE = 0, + ONE_EXACTLY = _MUST_APPEAR, + ZERO_OR_MORE = _CAN_REPEAT, + ONE_OR_MORE = _MUST_APPEAR | _CAN_REPEAT + } expected_count; + const node_callback_t callback; + size_t count; + + dictionary_entry_t(expected_count_t new_expected_count, node_callback_t new_callback) + : expected_count { new_expected_count }, callback { new_callback }, count { 0 } {} + + constexpr bool must_appear() const { + return static_cast<uint8_t>(expected_count) & static_cast<uint8_t>(expected_count_t::_MUST_APPEAR); + } + constexpr bool can_repeat() const { + return static_cast<uint8_t>(expected_count) & static_cast<uint8_t>(expected_count_t::_CAN_REPEAT); + } + }; + using enum dictionary_entry_t::expected_count_t; + using key_map_t = std::map<std::string, dictionary_entry_t, std::less<void>>; + + constexpr struct allow_other_keys_t {} ALLOW_OTHER_KEYS; + + node_callback_t _expect_dictionary_keys_and_length(length_callback_t length_callback, bool allow_other_keys, key_map_t&& key_map); + + template<typename... Args> + node_callback_t _expect_dictionary_keys_and_length(length_callback_t length_callback, + bool allow_other_keys, key_map_t&& key_map, + const std::string_view key, dictionary_entry_t::expected_count_t expected_count, node_callback_t callback, + Args... args) { + if (key_map.find(key) == key_map.end()) { + key_map.emplace(key, dictionary_entry_t { expected_count, callback }); + } else { + Logger::error("Duplicate expected dictionary key: ", key); + } + return _expect_dictionary_keys_and_length(length_callback, allow_other_keys, std::move(key_map), args...); + } + + template<typename... Args> + node_callback_t expect_dictionary_keys_and_length(length_callback_t length_callback, + const std::string_view key, dictionary_entry_t::expected_count_t expected_count, node_callback_t callback, + Args... args) { + return _expect_dictionary_keys_and_length(length_callback, false, {}, key, expected_count, callback, args...); + } + + template<typename... Args> + node_callback_t expect_dictionary_keys_and_length(length_callback_t length_callback, + allow_other_keys_t, Args... args) { + return _expect_dictionary_keys_and_length(length_callback, true, {}, args...); + } + + template<typename... Args> + node_callback_t expect_dictionary_keys(Args... args) { + return expect_dictionary_keys_and_length(default_length_callback, args...); + } + + template<typename T> + concept Reservable = requires(T& t) { + { t.size() } -> std::same_as<size_t>; + t.reserve(size_t {}); + }; + template<Reservable T> + node_callback_t expect_list_reserve_length(T& t, node_callback_t callback) { + return expect_list_and_length( + [&t](size_t size) -> size_t { + t.reserve(t.size() + size); + return size; + }, + callback + ); + } + template<Reservable T> + node_callback_t expect_dictionary_reserve_length(T& t, key_value_callback_t callback) { + return expect_list_reserve_length(t, expect_assign(callback)); + } + + node_callback_t name_list_callback(std::vector<std::string>& list); + + template<typename T> + callback_t<T> assign_variable_callback(T& var) { + return [&var](T val) -> bool { + var = val; + return true; + }; + } + + template<typename T> + requires(std::integral<T>) + callback_t<uint64_t> assign_variable_callback_uint(const std::string_view name, T& var) { + return [&var, name](uint64_t val) -> bool { + if (val <= std::numeric_limits<T>::max()) { + var = val; + return true; + } + Logger::error("Invalid ", name, ": ", val, " (valid range: [0, ", static_cast<uint64_t>(std::numeric_limits<T>::max()), "])"); + return false; + }; + } + + template<typename T> + requires(std::integral<T>) + callback_t<int64_t> assign_variable_callback_int(const std::string_view name, T& var) { + return [&var, name](int64_t val) -> bool { + if (std::numeric_limits<T>::lowest() <= val && val <= std::numeric_limits<T>::max()) { + var = val; + return true; + } + Logger::error("Invalid ", name, ": ", val, " (valid range: [", + static_cast<int64_t>(std::numeric_limits<T>::lowest()), ", ", + static_cast<uint64_t>(std::numeric_limits<T>::max()), "])"); + return false; + }; + } + } +} diff --git a/src/openvic-simulation/economy/Good.cpp b/src/openvic-simulation/economy/Good.cpp new file mode 100644 index 0000000..e3dbd3e --- /dev/null +++ b/src/openvic-simulation/economy/Good.cpp @@ -0,0 +1,140 @@ +#include "Good.hpp" + +#include <cassert> + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +GoodCategory::GoodCategory(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} + +Good::Good(const std::string_view new_identifier, colour_t new_colour, GoodCategory const& new_category, price_t new_base_price, + bool new_available_from_start, bool new_tradeable, bool new_money, bool new_overseas_penalty) + : HasIdentifierAndColour { new_identifier, new_colour, true }, + category { new_category }, + base_price { new_base_price }, + available_from_start { new_available_from_start }, + tradeable { new_tradeable }, + money { new_money }, + overseas_penalty { new_overseas_penalty } { + assert(base_price > NULL_PRICE); +} + +GoodCategory const& Good::get_category() const { + return category; +} + +Good::price_t Good::get_base_price() const { + return base_price; +} + +Good::price_t Good::get_price() const { + return price; +} + +bool Good::get_available_from_start() const { + return available_from_start; +} + +bool Good::get_available() const { + return available; +} + +bool Good::get_tradeable() const { + return tradeable; +} + +bool Good::get_money() const { + return money; +} + +bool Good::get_overseas_penalty() { + return overseas_penalty; +} + +void Good::reset_to_defaults() { + available = available_from_start; + price = base_price; +} + +GoodManager::GoodManager() : good_categories { "good categories" }, goods { "goods" } {} + +bool GoodManager::add_good_category(const std::string_view identifier) { + if (identifier.empty()) { + Logger::error("Invalid good category identifier - empty!"); + return false; + } + return good_categories.add_item({ identifier }); +} + +bool GoodManager::add_good(const std::string_view identifier, colour_t colour, GoodCategory const* category, + Good::price_t base_price, bool available_from_start, bool tradeable, bool money, bool overseas_penalty) { + if (identifier.empty()) { + Logger::error("Invalid good identifier - empty!"); + return false; + } + if (colour > MAX_COLOUR_RGB) { + Logger::error("Invalid good colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + if (category == nullptr) { + Logger::error("Invalid good category for ", identifier, ": null"); + return false; + } + if (base_price <= Good::NULL_PRICE) { + Logger::error("Invalid base price for ", identifier, ": ", base_price); + return false; + } + return goods.add_item({ identifier, colour, *category, base_price, available_from_start, tradeable, money, overseas_penalty }); +} + +void GoodManager::reset_to_defaults() { + for (Good& good : goods.get_items()) + good.reset_to_defaults(); +} + +bool GoodManager::load_good_file(ast::NodeCPtr root) { + size_t total_expected_goods = 0; + bool ret = expect_dictionary_reserve_length( + good_categories, + [this, &total_expected_goods](std::string_view key, ast::NodeCPtr value) -> bool { + bool ret = expect_list_and_length( + [&total_expected_goods](size_t size) -> size_t { + total_expected_goods += size; + return 0; + }, + success_callback + )(value); + ret &= add_good_category(key); + return ret; + } + )(root); + lock_good_categories(); + goods.reserve(goods.size() + total_expected_goods); + ret &= expect_dictionary( + [this](std::string_view good_category_key, ast::NodeCPtr good_category_value) -> bool { + GoodCategory const* good_category = get_good_category_by_identifier(good_category_key); + + return expect_dictionary( + [this, good_category](std::string_view key, ast::NodeCPtr value) -> bool { + colour_t colour = NULL_COLOUR; + Good::price_t base_price; + bool available_from_start, tradeable = true; + bool money, overseas_penalty = false; + + bool ret = expect_dictionary_keys( + "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), + "cost", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(base_price)), + "available_from_start", ZERO_OR_ONE, expect_bool(assign_variable_callback(available_from_start)), + "tradeable", ZERO_OR_ONE, expect_bool(assign_variable_callback(tradeable)), + "money", ZERO_OR_ONE, expect_bool(assign_variable_callback(money)), + "overseas_penalty", ZERO_OR_ONE, expect_bool(assign_variable_callback(overseas_penalty)) + )(value); + ret &= add_good(key, colour, good_category, base_price, available_from_start, tradeable, money, overseas_penalty); + return ret; + } + )(good_category_value); + } + )(root); + lock_goods(); + return ret; +} diff --git a/src/openvic/economy/Good.hpp b/src/openvic-simulation/economy/Good.hpp index cfe185d..ce97cad 100644 --- a/src/openvic/economy/Good.hpp +++ b/src/openvic-simulation/economy/Good.hpp @@ -1,10 +1,21 @@ #pragma once -#include "../Types.hpp" +#include "openvic-simulation/dataloader/NodeTools.hpp" +#include "openvic-simulation/types/IdentifierRegistry.hpp" namespace OpenVic { struct GoodManager; + struct GoodCategory : HasIdentifier { + friend struct GoodManager; + + private: + GoodCategory(const std::string_view new_identifier); + + public: + GoodCategory(GoodCategory&&) = default; + }; + /* REQUIREMENTS: * * ECON-3 , ECON-4 , ECON-5 , ECON-6 , ECON-7 , ECON-8 , ECON-9 , ECON-10, ECON-11, ECON-12, ECON-13, ECON-14, @@ -20,42 +31,49 @@ namespace OpenVic { struct Good : HasIdentifierAndColour { friend struct GoodManager; + using price_t = fixed_point_t; + static constexpr price_t NULL_PRICE = fixed_point_t::_0(); + private: - const std::string category; + GoodCategory const& category; const price_t base_price; price_t price; - const bool default_available, tradeable, currency, overseas_maintenance; + const bool available_from_start, tradeable, money, overseas_penalty; bool available; - Good(const std::string_view new_identifier, colour_t new_colour, const std::string_view new_category, price_t new_base_price, - bool new_default_available, bool new_tradeable, bool new_currency, bool new_overseas_maintenance); + Good(const std::string_view new_identifier, colour_t new_colour, GoodCategory const& new_category, price_t new_base_price, + bool new_available_from_start, bool new_tradeable, bool new_money, bool new_overseas_penalty); public: Good(Good&&) = default; - std::string const& get_category() const; + GoodCategory const& get_category() const; price_t get_base_price() const; price_t get_price() const; - bool is_default_available() const; - bool is_available() const; + bool get_available_from_start() const; + bool get_available() const; + bool get_tradeable() const; + bool get_money() const; + bool get_overseas_penalty(); void reset_to_defaults(); }; struct GoodManager { private: + IdentifierRegistry<GoodCategory> good_categories; IdentifierRegistry<Good> goods; public: GoodManager(); - return_t add_good(const std::string_view identifier, colour_t colour, const std::string_view category, price_t base_price, - bool default_available, bool tradeable, bool currency, bool overseas_maintenance); - void lock_goods(); - void reset_to_defaults(); + bool add_good_category(const std::string_view identifier); + IDENTIFIER_REGISTRY_ACCESSORS_CUSTOM_PLURAL(GoodCategory, good_category, good_categories) - Good const* get_good_by_index(size_t index) const; - Good const* get_good_by_identifier(const std::string_view identifier) const; - size_t get_good_count() const; - std::vector<Good> const& get_goods() const; + bool add_good(const std::string_view identifier, colour_t colour, GoodCategory const* category, Good::price_t base_price, + bool available_from_start, bool tradeable, bool money, bool overseas_penalty); + IDENTIFIER_REGISTRY_ACCESSORS(Good, good) + + void reset_to_defaults(); + bool load_good_file(ast::NodeCPtr root); }; } diff --git a/src/openvic/map/Building.cpp b/src/openvic-simulation/map/Building.cpp index 73a1886..6e5cf18 100644 --- a/src/openvic/map/Building.cpp +++ b/src/openvic-simulation/map/Building.cpp @@ -2,8 +2,8 @@ #include <cassert> -#include "../utility/Logger.hpp" -#include "Province.hpp" +#include "openvic-simulation/map/Province.hpp" +#include "openvic-simulation/utility/Logger.hpp" using namespace OpenVic; @@ -39,13 +39,13 @@ float Building::get_expansion_progress() const { return expansion_progress; } -return_t Building::expand() { +bool Building::expand() { if (expansion_state == ExpansionState::CanExpand) { expansion_state = ExpansionState::Preparing; expansion_progress = 0.0f; - return SUCCESS; + return true; } - return FAILURE; + return false; } /* REQUIREMENTS: @@ -94,42 +94,32 @@ Timespan BuildingType::get_build_time() const { BuildingManager::BuildingManager() : building_types { "building types" } {} -return_t BuildingManager::add_building_type(const std::string_view identifier, Building::level_t max_level, Timespan build_time) { +bool BuildingManager::add_building_type(const std::string_view identifier, Building::level_t max_level, Timespan build_time) { if (identifier.empty()) { Logger::error("Invalid building type identifier - empty!"); - return FAILURE; + return false; } if (max_level < 0) { Logger::error("Invalid building type max level for ", identifier, ": ", max_level); - return FAILURE; + return false; } if (build_time < 0) { Logger::error("Invalid building type build time for ", identifier, ": ", build_time); - return FAILURE; + return false; } return building_types.add_item({ identifier, max_level, build_time }); } -void BuildingManager::lock_building_types() { - building_types.lock(); -} - -BuildingType const* BuildingManager::get_building_type_by_identifier(const std::string_view identifier) const { - return building_types.get_item_by_identifier(identifier); -} - -return_t BuildingManager::generate_province_buildings(Province& province) const { +bool BuildingManager::generate_province_buildings(Province& province) const { province.reset_buildings(); if (!building_types.is_locked()) { Logger::error("Cannot generate buildings until building types are locked!"); - return FAILURE; + return false; } - return_t ret = SUCCESS; + bool ret = true; if (!province.is_water()) { for (BuildingType const& type : building_types.get_items()) { - if (province.add_building({ type }) != SUCCESS) { - ret = FAILURE; - } + ret &= province.add_building({ type }); } } province.lock_buildings(); diff --git a/src/openvic/map/Building.hpp b/src/openvic-simulation/map/Building.hpp index c00932b..d36dfd4 100644 --- a/src/openvic/map/Building.hpp +++ b/src/openvic-simulation/map/Building.hpp @@ -2,8 +2,8 @@ #include <vector> -#include "../Date.hpp" -#include "../Types.hpp" +#include "openvic-simulation/types/Date.hpp" +#include "openvic-simulation/types/IdentifierRegistry.hpp" namespace OpenVic { @@ -48,7 +48,7 @@ namespace OpenVic { Date const& get_end_date() const; float get_expansion_progress() const; - return_t expand(); + bool expand(); void update_state(Date const& today); void tick(Date const& today); }; @@ -78,9 +78,8 @@ namespace OpenVic { public: BuildingManager(); - return_t add_building_type(const std::string_view identifier, Building::level_t max_level, Timespan build_time); - void lock_building_types(); - BuildingType const* get_building_type_by_identifier(const std::string_view identifier) const; - return_t generate_province_buildings(Province& province) const; + bool add_building_type(const std::string_view identifier, Building::level_t max_level, Timespan build_time); + IDENTIFIER_REGISTRY_ACCESSORS(BuildingType, building_type) + bool generate_province_buildings(Province& province) const; }; } diff --git a/src/openvic/map/Map.cpp b/src/openvic-simulation/map/Map.cpp index 44c393d..728fc42 100644 --- a/src/openvic/map/Map.cpp +++ b/src/openvic-simulation/map/Map.cpp @@ -3,8 +3,8 @@ #include <cassert> #include <unordered_set> -#include "../economy/Good.hpp" -#include "../utility/Logger.hpp" +#include "openvic-simulation/economy/Good.hpp" +#include "openvic-simulation/utility/Logger.hpp" using namespace OpenVic; @@ -30,24 +30,24 @@ Map::Map() : provinces { "provinces" }, regions { "regions" }, mapmodes { "mapmodes" } {} -return_t Map::add_province(const std::string_view identifier, colour_t colour) { - if (provinces.get_item_count() >= MAX_INDEX) { - Logger::error("The map's province list is full - there can be at most ", MAX_INDEX, " provinces"); - return FAILURE; +bool Map::add_province(const std::string_view identifier, colour_t colour) { + if (provinces.size() >= max_provinces) { + Logger::error("The map's province list is full - maximum number of provinces is ", max_provinces, " (this can be at most ", Province::MAX_INDEX, ")"); + return false; } if (identifier.empty()) { Logger::error("Invalid province identifier - empty!"); - return FAILURE; + return false; } if (colour == NULL_COLOUR || colour > MAX_COLOUR_RGB) { - Logger::error("Invalid province colour for ", identifier, ": ", Province::colour_to_hex_string(colour)); - return FAILURE; + Logger::error("Invalid province colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; } - Province new_province { identifier, colour, static_cast<index_t>(provinces.get_item_count() + 1) }; - const index_t index = get_index_from_colour(colour); - if (index != NULL_INDEX) { + Province new_province { identifier, colour, static_cast<Province::index_t>(provinces.size() + 1) }; + const Province::index_t index = get_index_from_colour(colour); + if (index != Province::NULL_INDEX) { Logger::error("Duplicate province colours: ", get_province_by_index(index)->to_string(), " and ", new_province.to_string()); - return FAILURE; + return false; } colour_index_map[new_province.get_colour()] = new_province.get_index(); return provinces.add_item(std::move(new_province)); @@ -57,77 +57,86 @@ void Map::lock_provinces() { provinces.lock(); } -return_t Map::set_water_province(const std::string_view identifier) { +bool Map::set_water_province(const std::string_view identifier) { if (water_provinces.is_locked()) { Logger::error("The map's water provinces have already been locked!"); - return FAILURE; + return false; } Province* province = get_province_by_identifier(identifier); if (province == nullptr) { Logger::error("Unrecognised water province identifier: ", identifier); - return FAILURE; + return false; } if (province->is_water()) { - Logger::error("Province ", identifier, " is already a water province!"); - return FAILURE; + Logger::warning("Province ", identifier, " is already a water province!"); + return true; } - if (water_provinces.add_province(province) != SUCCESS) { + if (!water_provinces.add_province(province)) { Logger::error("Failed to add province ", identifier, " to water province set!"); - return FAILURE; + return false; } province->water = true; - return SUCCESS; + return true; +} + +bool Map::set_water_province_list(std::vector<std::string_view> const& list) { + bool ret = true; + water_provinces.reserve(water_provinces.size() + list.size()); + for (std::string_view const& identifier : list) { + ret &= set_water_province(identifier); + } + return ret; } void Map::lock_water_provinces() { water_provinces.lock(); - Logger::info("Locked water provinces after registering ", water_provinces.get_province_count()); + Logger::info("Locked water provinces after registering ", water_provinces.size()); } -return_t Map::add_region(const std::string_view identifier, std::vector<std::string_view> const& province_identifiers) { +bool Map::add_region(const std::string_view identifier, std::vector<std::string_view> const& province_identifiers) { if (identifier.empty()) { Logger::error("Invalid region identifier - empty!"); - return FAILURE; + return false; } Region new_region { identifier }; - return_t ret = SUCCESS; + bool ret = true; for (const std::string_view province_identifier : province_identifiers) { Province* province = get_province_by_identifier(province_identifier); if (province != nullptr) { if (new_region.contains_province(province)) { Logger::error("Duplicate province identifier ", province_identifier, " in region ", identifier); - ret = FAILURE; + ret = false; } else { size_t other_region_index = reinterpret_cast<size_t>(province->get_region()); if (other_region_index != 0) { other_region_index--; - if (other_region_index < regions.get_item_count()) + if (other_region_index < regions.size()) Logger::error("Cannot add province ", province_identifier, " to region ", identifier, " - it is already part of ", regions.get_item_by_index(other_region_index)->get_identifier()); else Logger::error("Cannot add province ", province_identifier, " to region ", identifier, " - it is already part of an unknown region with index ", other_region_index); - ret = FAILURE; - } else if (new_region.add_province(province) != SUCCESS) { + ret = false; + } else if (!new_region.add_province(province)) { Logger::error("Failed to add province ", province_identifier, " to region ", identifier); - ret = FAILURE; + ret = false; } } } else { Logger::error("Invalid province identifier ", province_identifier, " for region ", identifier); - ret = FAILURE; + ret = false; } } new_region.lock(); - if (!new_region.get_province_count()) { + if (new_region.empty()) { Logger::error("No valid provinces in list for ", identifier); - return FAILURE; + return false; } // Used to detect provinces listed in multiple regions, will // be corrected once regions is stable (i.e. lock_regions). - Region* tmp_region_index = reinterpret_cast<Region*>(regions.get_item_count()); + Region* tmp_region_index = reinterpret_cast<Region*>(regions.size()); for (Province* province : new_region.get_provinces()) province->region = tmp_region_index; - if (regions.add_item(std::move(new_region)) != SUCCESS) ret = FAILURE; + ret &= regions.add_item(std::move(new_region)); return ret; } @@ -139,15 +148,19 @@ void Map::lock_regions() { } size_t Map::get_province_count() const { - return provinces.get_item_count(); + return provinces.size(); +} + +std::vector<Province> const& Map::get_provinces() const { + return provinces.get_items(); } -Province* Map::get_province_by_index(index_t index) { - return index != NULL_INDEX ? provinces.get_item_by_index(index - 1) : nullptr; +Province* Map::get_province_by_index(Province::index_t index) { + return index != Province::NULL_INDEX ? provinces.get_item_by_index(index - 1) : nullptr; } -Province const* Map::get_province_by_index(index_t index) const { - return index != NULL_INDEX ? provinces.get_item_by_index(index - 1) : nullptr; +Province const* Map::get_province_by_index(Province::index_t index) const { + return index != Province::NULL_INDEX ? provinces.get_item_by_index(index - 1) : nullptr; } Province* Map::get_province_by_identifier(const std::string_view identifier) { @@ -158,27 +171,44 @@ Province const* Map::get_province_by_identifier(const std::string_view identifie return provinces.get_item_by_identifier(identifier); } -index_t Map::get_index_from_colour(colour_t colour) const { +Province::index_t Map::get_index_from_colour(colour_t colour) const { const colour_index_map_t::const_iterator it = colour_index_map.find(colour); if (it != colour_index_map.end()) return it->second; - return NULL_INDEX; + return Province::NULL_INDEX; } -index_t Map::get_province_index_at(size_t x, size_t y) const { +Province::index_t Map::get_province_index_at(size_t x, size_t y) const { if (x < width && y < height) return province_shape_image[x + y * width].index; - return NULL_INDEX; + return Province::NULL_INDEX; } -void Map::set_selected_province(index_t index) { +bool Map::set_max_provinces(Province::index_t new_max_provinces) { + if (new_max_provinces <= Province::NULL_INDEX) { + Logger::error("Trying to set max province count to an invalid value ", new_max_provinces, " (must be greater than ", Province::NULL_INDEX, ")"); + return false; + } + if (!provinces.empty() || provinces.is_locked()) { + Logger::error("Trying to set max province count to ", new_max_provinces, " after provinces have already been added and/or locked"); + return false; + } + max_provinces = new_max_provinces; + return true; +} + +Province::index_t Map::get_max_provinces() const { + return max_provinces; +} + +void Map::set_selected_province(Province::index_t index) { if (index > get_province_count()) { Logger::error("Trying to set selected province to an invalid index ", index, " (max index is ", get_province_count(), ")"); - selected_province = NULL_INDEX; + selected_province = Province::NULL_INDEX; } else { selected_province = index; } } -index_t Map::get_selected_province_index() const { +Province::index_t Map::get_selected_province_index() const { return selected_province; } @@ -194,39 +224,47 @@ Region const* Map::get_region_by_identifier(const std::string_view identifier) c return regions.get_item_by_identifier(identifier); } +size_t Map::get_region_count() const { + return regions.size(); +} + +std::vector<Region> const& Map::get_regions() const { + return regions.get_items(); +} + static colour_t colour_at(uint8_t const* colour_data, int32_t idx) { idx *= 3; return (colour_data[idx] << 16) | (colour_data[idx + 1] << 8) | colour_data[idx + 2]; } -return_t Map::generate_province_shape_image(size_t new_width, size_t new_height, uint8_t const* colour_data, +bool Map::generate_province_shape_image(size_t new_width, size_t new_height, uint8_t const* colour_data, uint8_t const* terrain_data, terrain_variant_map_t const& terrain_variant_map, bool detailed_errors) { if (!province_shape_image.empty()) { Logger::error("Province index image has already been generated!"); - return FAILURE; + return false; } if (!provinces.is_locked()) { Logger::error("Province index image cannot be generated until after provinces are locked!"); - return FAILURE; + return false; } if (new_width < 1 || new_height < 1) { Logger::error("Invalid province image dimensions: ", new_width, "x", new_height); - return FAILURE; + return false; } if (colour_data == nullptr) { Logger::error("Province colour data pointer is null!"); - return FAILURE; + return false; } if (terrain_data == nullptr) { Logger::error("Province terrain data pointer is null!"); - return FAILURE; + return false; } width = new_width; height = new_height; province_shape_image.resize(width * height); - std::vector<bool> province_checklist(provinces.get_item_count()); - return_t ret = SUCCESS; + std::vector<bool> province_checklist(provinces.size()); + bool ret = true; std::unordered_set<colour_t> unrecognised_province_colours, unrecognised_terrain_colours; for (int32_t y = 0; y < height; ++y) { @@ -240,7 +278,7 @@ return_t Map::generate_province_shape_image(size_t new_width, size_t new_height, if (unrecognised_terrain_colours.find(terrain_colour) == unrecognised_terrain_colours.end()) { unrecognised_terrain_colours.insert(terrain_colour); if (detailed_errors) { - Logger::error("Unrecognised terrain colour ", Province::colour_to_hex_string(terrain_colour), + Logger::warning("Unrecognised terrain colour ", colour_to_hex_string(terrain_colour), " at (", x, ", ", y, ")"); } } @@ -262,8 +300,8 @@ return_t Map::generate_province_shape_image(size_t new_width, size_t new_height, continue; } } - const index_t index = get_index_from_colour(province_colour); - if (index != NULL_INDEX) { + const Province::index_t index = get_index_from_colour(province_colour); + if (index != Province::NULL_INDEX) { province_checklist[index - 1] = true; province_shape_image[idx].index = index; continue; @@ -271,20 +309,18 @@ return_t Map::generate_province_shape_image(size_t new_width, size_t new_height, if (unrecognised_province_colours.find(province_colour) == unrecognised_province_colours.end()) { unrecognised_province_colours.insert(province_colour); if (detailed_errors) { - Logger::error("Unrecognised province colour ", Province::colour_to_hex_string(province_colour), + Logger::warning("Unrecognised province colour ", colour_to_hex_string(province_colour), " at (", x, ", ", y, ")"); } } - province_shape_image[idx].index = NULL_INDEX; + province_shape_image[idx].index = Province::NULL_INDEX; } } if (!unrecognised_province_colours.empty()) { - Logger::error("Province image contains ", unrecognised_province_colours.size(), " unrecognised province colours"); - ret = FAILURE; + Logger::warning("Province image contains ", unrecognised_province_colours.size(), " unrecognised province colours"); } if (!unrecognised_terrain_colours.empty()) { - Logger::error("Terrain image contains ", unrecognised_terrain_colours.size(), " unrecognised terrain colours"); - ret = FAILURE; + Logger::warning("Terrain image contains ", unrecognised_terrain_colours.size(), " unrecognised terrain colours"); } size_t missing = 0; @@ -298,7 +334,7 @@ return_t Map::generate_province_shape_image(size_t new_width, size_t new_height, } if (missing > 0) { Logger::error("Province image is missing ", missing, " province colours"); - ret = FAILURE; + ret = false; } return ret; } @@ -315,48 +351,32 @@ std::vector<Map::shape_pixel_t> const& Map::get_province_shape_image() const { return province_shape_image; } -return_t Map::add_mapmode(const std::string_view identifier, Mapmode::colour_func_t colour_func) { +bool Map::add_mapmode(const std::string_view identifier, Mapmode::colour_func_t colour_func) { if (identifier.empty()) { Logger::error("Invalid mapmode identifier - empty!"); - return FAILURE; + return false; } if (colour_func == nullptr) { Logger::error("Mapmode colour function is null for identifier: ", identifier); - return FAILURE; + return false; } - return mapmodes.add_item({ identifier, mapmodes.get_item_count(), colour_func }); -} - -void Map::lock_mapmodes() { - mapmodes.lock(); -} - -size_t Map::get_mapmode_count() const { - return mapmodes.get_item_count(); -} - -Mapmode const* Map::get_mapmode_by_index(size_t index) const { - return mapmodes.get_item_by_index(index); -} - -Mapmode const* Map::get_mapmode_by_identifier(const std::string_view identifier) const { - return mapmodes.get_item_by_identifier(identifier); + return mapmodes.add_item({ identifier, mapmodes.size(), colour_func }); } -return_t Map::generate_mapmode_colours(Mapmode::index_t index, uint8_t* target) const { +bool Map::generate_mapmode_colours(Mapmode::index_t index, uint8_t* target) const { if (target == nullptr) { Logger::error("Mapmode colour target pointer is null!"); - return FAILURE; + return false; } - return_t ret = SUCCESS; + bool ret = true; Mapmode const* mapmode = mapmodes.get_item_by_index(index); if (mapmode == nullptr) { // Not an error if mapmodes haven't yet been loaded, // e.g. if we want to allocate the province colour // texture before mapmodes are loaded. - if (!(mapmodes.get_item_count() == 0 && index == 0)) { + if (!(mapmodes.empty() && index == 0)) { Logger::error("Invalid mapmode index: ", index); - ret = FAILURE; + ret = false; } mapmode = &Mapmode::ERROR_MAPMODE; } @@ -395,17 +415,14 @@ Pop::pop_size_t Map::get_total_map_population() const { return total_map_population; } -return_t Map::setup(GoodManager const& good_manager, BuildingManager const& building_manager, PopManager const& pop_manager) { - return_t ret = SUCCESS; +bool Map::setup(GoodManager const& good_manager, BuildingManager const& building_manager, PopManager const& pop_manager) { + bool ret = true; for (Province& province : provinces.get_items()) { province.clear_pops(); // Set all land provinces to have an RGO based on their index to test them if (!province.is_water() && good_manager.get_good_count() > 0) province.rgo = good_manager.get_good_by_index(province.get_index() % good_manager.get_good_count()); - if (building_manager.generate_province_buildings(province) != SUCCESS) ret = FAILURE; - // Add some pops to the province (for testing purposes) - if (!province.is_water()) - pop_manager.generate_test_pops(province); + ret &= building_manager.generate_province_buildings(province); } return ret; } @@ -421,3 +438,68 @@ void Map::tick(Date const& today) { for (Province& province : provinces.get_items()) province.tick(today); } + +using namespace ovdl::csv; + +static bool validate_province_definitions_header(LineObject const& header) { + static const std::vector<std::string> standard_header { "province", "red", "green", "blue" }; + for (size_t i = 0; i < standard_header.size(); ++i) { + const std::string_view val = header.get_value_for(i); + if (i == 0 && val.empty()) break; + if (val != standard_header[i]) return false; + } + return true; +} + +static bool parse_province_colour(colour_t& colour, std::array<std::string_view, 3> components) { + bool ret = true; + colour = NULL_COLOUR; + for (std::string_view& c : components) { + colour <<= 8; + if (c.ends_with('.')) c.remove_suffix(1); + bool successful = false; + uint64_t val = StringUtils::string_to_uint64(c, &successful, 10); + if (successful && val <= 255) { + colour |= val; + } else { + ret = false; + } + } + return ret; +} + +bool Map::load_province_definitions(std::vector<LineObject> const& lines) { + if (lines.empty()) { + Logger::error("No header or entries in province definition file!"); + return false; + } + { + LineObject const& header = lines.front(); + if (!validate_province_definitions_header(header)) { + Logger::error("Non-standard province definition file header - make sure this is not a province definition: ", header); + } + } + if (lines.size() <= 1) { + Logger::error("No entries in province definition file!"); + return false; + } + provinces.reserve(lines.size() - 1); + bool ret = true; + std::for_each(lines.begin() + 1, lines.end(), + [this, &ret](LineObject const& line) -> void { + const std::string_view identifier = line.get_value_for(0); + if (!identifier.empty()) { + colour_t colour; + if (!parse_province_colour(colour, + { line.get_value_for(1), line.get_value_for(2), line.get_value_for(3) } + )) { + Logger::error("Error reading colour in province definition: ", line); + ret = false; + } + ret &= add_province(identifier, colour); + } + } + ); + lock_provinces(); + return ret; +} diff --git a/src/openvic/map/Map.hpp b/src/openvic-simulation/map/Map.hpp index 64678c8..71719e2 100644 --- a/src/openvic/map/Map.hpp +++ b/src/openvic-simulation/map/Map.hpp @@ -2,7 +2,9 @@ #include <functional> -#include "Region.hpp" +#include <openvic-dataloader/csv/LineObject.hpp> + +#include "openvic-simulation/map/Region.hpp" namespace OpenVic { @@ -21,6 +23,8 @@ namespace OpenVic { public: static const Mapmode ERROR_MAPMODE; + Mapmode(Mapmode&&) = default; + index_t get_index() const; colour_t get_colour(Map const& map, Province const& province) const; }; @@ -36,12 +40,12 @@ namespace OpenVic { #pragma pack(push, 1) struct shape_pixel_t { - index_t index; + Province::index_t index; terrain_t terrain; }; #pragma pack(pop) private: - using colour_index_map_t = std::map<colour_t, index_t>; + using colour_index_map_t = std::map<colour_t, Province::index_t>; IdentifierRegistry<Province> provinces; IdentifierRegistry<Region> regions; @@ -51,50 +55,54 @@ namespace OpenVic { size_t width = 0, height = 0; std::vector<shape_pixel_t> province_shape_image; colour_index_map_t colour_index_map; - index_t selected_province = NULL_INDEX; + Province::index_t max_provinces = Province::MAX_INDEX; + Province::index_t selected_province = Province::NULL_INDEX; Pop::pop_size_t highest_province_population, total_map_population; - index_t get_index_from_colour(colour_t colour) const; + Province::index_t get_index_from_colour(colour_t colour) const; public: Map(); - return_t add_province(const std::string_view identifier, colour_t colour); + bool add_province(const std::string_view identifier, colour_t colour); void lock_provinces(); - return_t set_water_province(const std::string_view identifier); + bool set_water_province(const std::string_view identifier); + bool set_water_province_list(std::vector<std::string_view> const& list); void lock_water_provinces(); - return_t add_region(const std::string_view identifier, std::vector<std::string_view> const& province_identifiers); + bool add_region(const std::string_view identifier, std::vector<std::string_view> const& province_identifiers); void lock_regions(); size_t get_province_count() const; - Province* get_province_by_index(index_t index); - Province const* get_province_by_index(index_t index) const; + std::vector<Province> const& get_provinces() const; + Province* get_province_by_index(Province::index_t index); + Province const* get_province_by_index(Province::index_t index) const; Province* get_province_by_identifier(const std::string_view identifier); Province const* get_province_by_identifier(const std::string_view identifier) const; - index_t get_province_index_at(size_t x, size_t y) const; - void set_selected_province(index_t index); - index_t get_selected_province_index() const; + Province::index_t get_province_index_at(size_t x, size_t y) const; + bool set_max_provinces(Province::index_t new_max_provinces); + Province::index_t get_max_provinces() const; + void set_selected_province(Province::index_t index); + Province::index_t get_selected_province_index() const; Province const* get_selected_province() const; Region* get_region_by_identifier(const std::string_view identifier); Region const* get_region_by_identifier(const std::string_view identifier) const; + size_t get_region_count() const; + std::vector<Region> const& get_regions() const; - return_t generate_province_shape_image(size_t new_width, size_t new_height, uint8_t const* colour_data, + bool generate_province_shape_image(size_t new_width, size_t new_height, uint8_t const* colour_data, uint8_t const* terrain_data, terrain_variant_map_t const& terrain_variant_map, bool detailed_errors); size_t get_width() const; size_t get_height() const; std::vector<shape_pixel_t> const& get_province_shape_image() const; - return_t add_mapmode(const std::string_view identifier, Mapmode::colour_func_t colour_func); - void lock_mapmodes(); - size_t get_mapmode_count() const; - Mapmode const* get_mapmode_by_index(Mapmode::index_t index) const; - Mapmode const* get_mapmode_by_identifier(const std::string_view identifier) const; + bool add_mapmode(const std::string_view identifier, Mapmode::colour_func_t colour_func); + IDENTIFIER_REGISTRY_ACCESSORS(Mapmode, mapmode) static constexpr size_t MAPMODE_COLOUR_SIZE = 4; - return_t generate_mapmode_colours(Mapmode::index_t index, uint8_t* target) const; + bool generate_mapmode_colours(Mapmode::index_t index, uint8_t* target) const; - return_t setup(GoodManager const& good_manager, BuildingManager const& building_manager, PopManager const& pop_manager); + bool setup(GoodManager const& good_manager, BuildingManager const& building_manager, PopManager const& pop_manager); void update_highest_province_population(); Pop::pop_size_t get_highest_province_population() const; @@ -103,5 +111,7 @@ namespace OpenVic { void update_state(Date const& today); void tick(Date const& today); + + bool load_province_definitions(std::vector<ovdl::csv::LineObject> const& lines); }; } diff --git a/src/openvic/map/Province.cpp b/src/openvic-simulation/map/Province.cpp index ea284b6..f53de3a 100644 --- a/src/openvic/map/Province.cpp +++ b/src/openvic-simulation/map/Province.cpp @@ -5,15 +5,16 @@ #include <sstream> using namespace OpenVic; +using namespace OpenVic::NodeTools; Province::Province(const std::string_view new_identifier, colour_t new_colour, index_t new_index) : HasIdentifierAndColour { new_identifier, new_colour, false }, index { new_index }, - buildings { "buildings" } { + buildings { "buildings", false } { assert(index != NULL_INDEX); } -index_t Province::get_index() const { +Province::index_t Province::get_index() const { return index; } @@ -29,29 +30,17 @@ Province::life_rating_t Province::get_life_rating() const { return life_rating; } -return_t Province::add_building(Building&& building) { +bool Province::add_building(Building&& building) { return buildings.add_item(std::move(building)); } -void Province::lock_buildings() { - buildings.lock(false); -} - void Province::reset_buildings() { buildings.reset(); } -Building const* Province::get_building_by_identifier(const std::string_view identifier) const { - return buildings.get_item_by_identifier(identifier); -} - -std::vector<Building> const& Province::get_buildings() const { - return buildings.get_items(); -} - -return_t Province::expand_building(const std::string_view building_type_identifier) { +bool Province::expand_building(const std::string_view building_type_identifier) { Building* building = buildings.get_item_by_identifier(building_type_identifier); - if (building == nullptr) return FAILURE; + if (building == nullptr) return false; return building->expand(); } @@ -65,11 +54,22 @@ std::string Province::to_string() const { return stream.str(); } -void Province::add_pop(Pop&& pop) { - if (is_water()) { - Logger::error("Trying to add pop to water province ", get_identifier()); - } else { +bool Province::load_pop_list(PopManager const& pop_manager, ast::NodeCPtr root) { + return expect_dictionary_reserve_length( + pops, + [this, &pop_manager](std::string_view pop_type_identifier, ast::NodeCPtr pop_node) -> bool { + return pop_manager.load_pop_into_province(*this, pop_type_identifier, pop_node); + } + )(root); +} + +bool Province::add_pop(Pop&& pop) { + if (!is_water()) { pops.push_back(std::move(pop)); + return true; + } else { + Logger::error("Trying to add pop to water province ", get_identifier()); + return false; } } @@ -77,6 +77,14 @@ void Province::clear_pops() { pops.clear(); } +size_t Province::get_pop_count() const { + return pops.size(); +} + +std::vector<Pop> const& Province::get_pops() const { + return pops; +} + Pop::pop_size_t Province::get_total_population() const { return total_population; } @@ -89,17 +97,23 @@ distribution_t const& Province::get_culture_distribution() const { return cultures; } +distribution_t const& Province::get_religion_distribution() const { + return religions; +} + /* REQUIREMENTS: - * MAP-65 + * MAP-65, MAP-68, MAP-70, MAP-234 */ void Province::update_pops() { total_population = 0; pop_types.clear(); cultures.clear(); + religions.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(); } } diff --git a/src/openvic/map/Province.hpp b/src/openvic-simulation/map/Province.hpp index 20c5870..67816ff 100644 --- a/src/openvic/map/Province.hpp +++ b/src/openvic-simulation/map/Province.hpp @@ -1,7 +1,7 @@ #pragma once -#include "../pop/Pop.hpp" -#include "Building.hpp" +#include "openvic-simulation/map/Building.hpp" +#include "openvic-simulation/pop/Pop.hpp" namespace OpenVic { struct Map; @@ -10,12 +10,16 @@ namespace OpenVic { /* REQUIREMENTS: * MAP-5, MAP-7, MAP-8, MAP-43, MAP-47 + * POP-22 */ struct Province : HasIdentifierAndColour { friend struct Map; + using index_t = uint16_t; using life_rating_t = int8_t; + static constexpr index_t NULL_INDEX = 0, MAX_INDEX = (1 << (8 * sizeof(index_t))) - 1; + private: const index_t index; Region* region = nullptr; @@ -27,7 +31,7 @@ namespace OpenVic { std::vector<Pop> pops; Pop::pop_size_t total_population; - distribution_t pop_types, cultures; + distribution_t pop_types, cultures, religions; Province(const std::string_view new_identifier, colour_t new_colour, index_t new_index); @@ -38,20 +42,22 @@ namespace OpenVic { Region* get_region() const; bool is_water() const; life_rating_t get_life_rating() const; - return_t add_building(Building&& building); - void lock_buildings(); + bool add_building(Building&& building); + IDENTIFIER_REGISTRY_ACCESSORS(Building, building) void reset_buildings(); - Building const* get_building_by_identifier(const std::string_view identifier) const; - std::vector<Building> const& get_buildings() const; - return_t expand_building(const std::string_view building_type_identifier); + bool expand_building(const std::string_view building_type_identifier); Good const* get_rgo() const; std::string to_string() const; - void add_pop(Pop&& pop); + bool load_pop_list(PopManager const& pop_manager, ast::NodeCPtr root); + bool add_pop(Pop&& pop); void clear_pops(); + size_t get_pop_count() const; + std::vector<Pop> 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 const& today); diff --git a/src/openvic/map/Region.cpp b/src/openvic-simulation/map/Region.cpp index fc207e3..33092c5 100644 --- a/src/openvic/map/Region.cpp +++ b/src/openvic-simulation/map/Region.cpp @@ -1,24 +1,22 @@ #include "Region.hpp" -#include <cassert> - using namespace OpenVic; -return_t ProvinceSet::add_province(Province* province) { +bool ProvinceSet::add_province(Province* province) { if (locked) { Logger::error("Cannot add province to province set - locked!"); - return FAILURE; + return false; } if (province == nullptr) { Logger::error("Cannot add province to province set - null province!"); - return FAILURE; + return false; } if (contains_province(province)) { Logger::error("Cannot add province ", province->get_identifier(), " to province set - already in the set!"); - return FAILURE; + return false; } provinces.push_back(province); - return SUCCESS; + return true; } void ProvinceSet::lock(bool log) { @@ -26,7 +24,7 @@ void ProvinceSet::lock(bool log) { Logger::error("Failed to lock province set - already locked!"); } else { locked = true; - if (log) Logger::info("Locked province set with ", get_province_count(), " provinces"); + if (log) Logger::info("Locked province set with ", size(), " provinces"); } } @@ -39,10 +37,22 @@ void ProvinceSet::reset() { locked = false; } -size_t ProvinceSet::get_province_count() const { +bool ProvinceSet::empty() const { + return provinces.empty(); +} + +size_t ProvinceSet::size() const { return provinces.size(); } +void ProvinceSet::reserve(size_t size) { + if (locked) { + Logger::error("Failed to reserve space for ", size, " items in province set - already locked!"); + } else { + provinces.reserve(size); + } +} + bool ProvinceSet::contains_province(Province const* province) const { return province && std::find(provinces.begin(), provinces.end(), province) != provinces.end(); } diff --git a/src/openvic/map/Region.hpp b/src/openvic-simulation/map/Region.hpp index beeb76e..2fccf06 100644 --- a/src/openvic/map/Region.hpp +++ b/src/openvic-simulation/map/Region.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Province.hpp" +#include "openvic-simulation/map/Province.hpp" namespace OpenVic { @@ -10,11 +10,13 @@ namespace OpenVic { bool locked = false; public: - return_t add_province(Province* province); + bool add_province(Province* province); void lock(bool log = false); bool is_locked() const; void reset(); - size_t get_province_count() const; + bool empty() const; + size_t size() const; + void reserve(size_t size); bool contains_province(Province const* province) const; std::vector<Province*> const& get_provinces() const; }; diff --git a/src/openvic-simulation/pop/Culture.cpp b/src/openvic-simulation/pop/Culture.cpp new file mode 100644 index 0000000..709f305 --- /dev/null +++ b/src/openvic-simulation/pop/Culture.cpp @@ -0,0 +1,218 @@ +#include "Culture.hpp" + +#include "openvic-simulation/dataloader/NodeTools.hpp" + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +GraphicalCultureType::GraphicalCultureType(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} + +CultureGroup::CultureGroup(const std::string_view new_identifier, const std::string_view new_leader, + GraphicalCultureType const& new_unit_graphical_culture_type, bool new_is_overseas) + : HasIdentifier { new_identifier }, leader { new_leader }, + unit_graphical_culture_type { new_unit_graphical_culture_type }, + is_overseas { new_is_overseas } {} + +std::string const& CultureGroup::get_leader() const { + return leader; +} + +GraphicalCultureType const& CultureGroup::get_unit_graphical_culture_type() const { + return unit_graphical_culture_type; +} + +bool CultureGroup::get_is_overseas() const { + return is_overseas; +} + +Culture::Culture(const std::string_view new_identifier, colour_t new_colour, CultureGroup const& new_group, + std::vector<std::string> const& new_first_names, std::vector<std::string> const& new_last_names) + : HasIdentifierAndColour { new_identifier, new_colour, true }, + group { new_group }, + first_names { new_first_names }, + last_names { new_last_names } {} + +CultureGroup const& Culture::get_group() const { + return group; +} + +std::vector<std::string> const& Culture::get_first_names() const { + return first_names; +} + +std::vector<std::string> const& Culture::get_last_names() const { + return last_names; +} + +CultureManager::CultureManager() + : graphical_culture_types { "graphical culture types" }, + culture_groups { "culture groups" }, + cultures { "cultures" } {} + +bool CultureManager::add_graphical_culture_type(const std::string_view identifier) { + if (identifier.empty()) { + Logger::error("Invalid culture group identifier - empty!"); + return false; + } + return graphical_culture_types.add_item({ identifier }); +} + +bool CultureManager::add_culture_group(const std::string_view identifier, const std::string_view leader, GraphicalCultureType const* graphical_culture_type, bool is_overseas) { + if (!graphical_culture_types.is_locked()) { + Logger::error("Cannot register culture groups until graphical culture types are locked!"); + return false; + } + if (identifier.empty()) { + Logger::error("Invalid culture group identifier - empty!"); + return false; + } + if (leader.empty()) { + Logger::error("Invalid culture group leader - empty!"); + return false; + } + if (graphical_culture_type == nullptr) { + Logger::error("Null graphical culture type for ", identifier); + return false; + } + return culture_groups.add_item({ identifier, leader, *graphical_culture_type, is_overseas }); +} + +bool CultureManager::add_culture(const std::string_view identifier, colour_t colour, CultureGroup const* group, std::vector<std::string> const& first_names, std::vector<std::string> const& last_names) { + if (!culture_groups.is_locked()) { + Logger::error("Cannot register cultures until culture groups are locked!"); + return false; + } + if (identifier.empty()) { + Logger::error("Invalid culture identifier - empty!"); + return false; + } + if (group == nullptr) { + Logger::error("Null culture group for ", identifier); + return false; + } + if (colour > MAX_COLOUR_RGB) { + Logger::error("Invalid culture colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + return cultures.add_item({ identifier, colour, *group, first_names, last_names }); +} + +bool CultureManager::load_graphical_culture_type_file(ast::NodeCPtr root) { + const bool ret = expect_list_reserve_length( + graphical_culture_types, + expect_identifier( + std::bind(&CultureManager::add_graphical_culture_type, this, std::placeholders::_1) + ) + )(root); + lock_graphical_culture_types(); + return ret; +} + +bool CultureManager::_load_culture_group(size_t& total_expected_cultures, + GraphicalCultureType const* default_unit_graphical_culture_type, + const std::string_view culture_group_key, ast::NodeCPtr culture_group_node) { + + std::string_view leader; + GraphicalCultureType const* unit_graphical_culture_type = default_unit_graphical_culture_type; + bool is_overseas = true; + + bool ret = expect_dictionary_keys_and_length( + [&total_expected_cultures](size_t size) -> size_t { + total_expected_cultures += size; + return size; + }, + ALLOW_OTHER_KEYS, + "leader", ONE_EXACTLY, [&total_expected_cultures, &leader](ast::NodeCPtr node) -> bool { + total_expected_cultures--; + return expect_identifier(assign_variable_callback(leader))(node); + }, + "unit", ZERO_OR_ONE, [this, &total_expected_cultures, &unit_graphical_culture_type](ast::NodeCPtr node) -> bool { + total_expected_cultures--; + return expect_graphical_culture_type(unit_graphical_culture_type)(node); + }, + "union", ZERO_OR_ONE, [&total_expected_cultures](ast::NodeCPtr) -> bool { + total_expected_cultures--; + return true; + }, + "is_overseas", ZERO_OR_ONE, [&total_expected_cultures, &is_overseas](ast::NodeCPtr node) -> bool { + total_expected_cultures--; + return expect_bool(assign_variable_callback(is_overseas))(node); + } + )(culture_group_node); + ret &= add_culture_group(culture_group_key, leader, unit_graphical_culture_type, is_overseas); + return ret; +} + +bool CultureManager::_load_culture(CultureGroup const* culture_group, + const std::string_view culture_key, ast::NodeCPtr culture_node) { + + colour_t colour = NULL_COLOUR; + std::vector<std::string> first_names, last_names; + + bool ret = expect_dictionary_keys( + "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), + "first_names", ONE_EXACTLY, name_list_callback(first_names), + "last_names", ONE_EXACTLY, name_list_callback(last_names), + "radicalism", ZERO_OR_ONE, success_callback, + "primary", ZERO_OR_ONE, success_callback + )(culture_node); + ret &= add_culture(culture_key, colour, culture_group, first_names, last_names); + return ret; +} + +/* REQUIREMENTS: + * POP-59, POP-60, POP-61, POP-62, POP-63, POP-64, POP-65, POP-66, POP-67, POP-68, POP-69, POP-70, POP-71, + * POP-72, POP-73, POP-74, POP-75, POP-76, POP-77, POP-78, POP-79, POP-80, POP-81, POP-82, POP-83, POP-84, + * POP-85, POP-86, POP-87, POP-88, POP-89, POP-90, POP-91, POP-92, POP-93, POP-94, POP-95, POP-96, POP-97, + * POP-98, POP-99, POP-100, POP-101, POP-102, POP-103, POP-104, POP-105, POP-106, POP-107, POP-108, POP-109, POP-110, + * POP-111, POP-112, POP-113, POP-114, POP-115, POP-116, POP-117, POP-118, POP-119, POP-120, POP-121, POP-122, POP-123, + * POP-124, POP-125, POP-126, POP-127, POP-128, POP-129, POP-130, POP-131, POP-132, POP-133, POP-134, POP-135, POP-136, + * POP-137, POP-138, POP-139, POP-140, POP-141, POP-142, POP-143, POP-144, POP-145, POP-146, POP-147, POP-148, POP-149, + * POP-150, POP-151, POP-152, POP-153, POP-154, POP-155, POP-156, POP-157, POP-158, POP-159, POP-160, POP-161, POP-162, + * POP-163, POP-164, POP-165, POP-166, POP-167, POP-168, POP-169, POP-170, POP-171, POP-172, POP-173, POP-174, POP-175, + * POP-176, POP-177, POP-178, POP-179, POP-180, POP-181, POP-182, POP-183, POP-184, POP-185, POP-186, POP-187, POP-188, + * POP-189, POP-190, POP-191, POP-192, POP-193, POP-194, POP-195, POP-196, POP-197, POP-198, POP-199, POP-200, POP-201, + * POP-202, POP-203, POP-204, POP-205, POP-206, POP-207, POP-208, POP-209, POP-210, POP-211, POP-212, POP-213, POP-214, + * POP-215, POP-216, POP-217, POP-218, POP-219, POP-220, POP-221, POP-222, POP-223, POP-224, POP-225, POP-226, POP-227, + * POP-228, POP-229, POP-230, POP-231, POP-232, POP-233, POP-234, POP-235, POP-236, POP-237, POP-238, POP-239, POP-240, + * POP-241, POP-242, POP-243, POP-244, POP-245, POP-246, POP-247, POP-248, POP-249, POP-250, POP-251, POP-252, POP-253, + * POP-254, POP-255, POP-256, POP-257, POP-258, POP-259, POP-260, POP-261, POP-262, POP-263, POP-264, POP-265, POP-266, + * POP-267, POP-268, POP-269, POP-270, POP-271, POP-272, POP-273, POP-274, POP-275, POP-276, POP-277, POP-278, POP-279, + * POP-280, POP-281, POP-282, POP-283, POP-284 + */ +bool CultureManager::load_culture_file(ast::NodeCPtr root) { + if (!graphical_culture_types.is_locked()) { + Logger::error("Cannot load culture groups until graphical culture types are locked!"); + return false; + } + + static const std::string 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) { + Logger::error("Failed to find default unit graphical culture type: ", default_unit_graphical_culture_type_identifier); + } + + size_t total_expected_cultures = 0; + bool ret = expect_dictionary_reserve_length( + culture_groups, + [this, default_unit_graphical_culture_type, &total_expected_cultures](std::string_view key, ast::NodeCPtr value) -> bool { + return _load_culture_group(total_expected_cultures, default_unit_graphical_culture_type, key, value); + } + )(root); + lock_culture_groups(); + cultures.reserve(cultures.size() + total_expected_cultures); + + ret &= expect_dictionary( + [this](std::string_view culture_group_key, ast::NodeCPtr culture_group_value) -> bool { + CultureGroup const* culture_group = get_culture_group_by_identifier(culture_group_key); + return expect_dictionary( + [this, culture_group](std::string_view key, ast::NodeCPtr value) -> bool { + if (key == "leader" || key == "unit" || key == "union" || key == "is_overseas") return true; + return _load_culture(culture_group, key, value); + } + )(culture_group_value); + } + )(root); + lock_cultures(); + return ret; +} diff --git a/src/openvic-simulation/pop/Culture.hpp b/src/openvic-simulation/pop/Culture.hpp new file mode 100644 index 0000000..d27c7c9 --- /dev/null +++ b/src/openvic-simulation/pop/Culture.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "openvic-simulation/types/IdentifierRegistry.hpp" + +namespace OpenVic { + + struct CultureManager; + + struct GraphicalCultureType : HasIdentifier { + friend struct CultureManager; + + private: + GraphicalCultureType(const std::string_view new_identifier); + + public: + GraphicalCultureType(GraphicalCultureType&&) = default; + }; + + struct CultureGroup : HasIdentifier { + friend struct CultureManager; + + private: + const std::string leader; + GraphicalCultureType const& unit_graphical_culture_type; + const bool is_overseas; + + // TODO - union tag + + CultureGroup(const std::string_view new_identifier, const std::string_view new_leader, GraphicalCultureType const& new_unit_graphical_culture_type, bool new_is_overseas); + + public: + CultureGroup(CultureGroup&&) = default; + + std::string const& get_leader() const; + GraphicalCultureType const& get_unit_graphical_culture_type() const; + bool get_is_overseas() const; + }; + + struct Culture : HasIdentifierAndColour { + friend struct CultureManager; + + private: + CultureGroup const& group; + const std::vector<std::string> first_names, last_names; + + // TODO - radicalism, primary tag + + Culture(const std::string_view new_identifier, colour_t new_colour, CultureGroup const& new_group, std::vector<std::string> const& new_first_names, std::vector<std::string> const& new_last_names); + + public: + Culture(Culture&&) = default; + + CultureGroup const& get_group() const; + std::vector<std::string> const& get_first_names() const; + std::vector<std::string> const& get_last_names() const; + }; + + struct CultureManager { + private: + IdentifierRegistry<GraphicalCultureType> graphical_culture_types; + IdentifierRegistry<CultureGroup> culture_groups; + IdentifierRegistry<Culture> cultures; + + bool _load_culture_group(size_t& total_expected_cultures, GraphicalCultureType const* default_unit_graphical_culture_type, + const std::string_view culture_group_key, ast::NodeCPtr culture_group_node); + bool _load_culture(CultureGroup const* culture_group, const std::string_view culture_key, ast::NodeCPtr node); + + public: + CultureManager(); + + bool add_graphical_culture_type(const std::string_view identifier); + IDENTIFIER_REGISTRY_ACCESSORS(GraphicalCultureType, graphical_culture_type) + + bool add_culture_group(const std::string_view identifier, const std::string_view leader, GraphicalCultureType const* new_graphical_culture_type, bool is_overseas); + IDENTIFIER_REGISTRY_ACCESSORS(CultureGroup, culture_group) + + bool add_culture(const std::string_view identifier, colour_t colour, CultureGroup const* group, std::vector<std::string> const& first_names, std::vector<std::string> const& last_names); + IDENTIFIER_REGISTRY_ACCESSORS(Culture, culture) + + bool load_graphical_culture_type_file(ast::NodeCPtr root); + bool load_culture_file(ast::NodeCPtr root); + }; +} diff --git a/src/openvic-simulation/pop/Pop.cpp b/src/openvic-simulation/pop/Pop.cpp new file mode 100644 index 0000000..f8e7254 --- /dev/null +++ b/src/openvic-simulation/pop/Pop.cpp @@ -0,0 +1,158 @@ +#include "Pop.hpp" + +#include <cassert> + +#include "openvic-simulation/dataloader/NodeTools.hpp" +#include "openvic-simulation/map/Province.hpp" +#include "openvic-simulation/utility/Logger.hpp" + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +Pop::Pop(PopType const& new_type, Culture const& new_culture, Religion const& new_religion, pop_size_t new_size) + : type { new_type }, + culture { new_culture }, + religion { new_religion }, + size { new_size } { + assert(size > 0); +} + +PopType const& Pop::get_type() const { + return type; +} + +Culture const& Pop::get_culture() const { + return culture; +} + +Religion const& Pop::get_religion() const { + return religion; +} + +Pop::pop_size_t Pop::get_size() const { + return size; +} + +Pop::pop_size_t Pop::get_num_promoted() const { + return num_promoted; +} + +Pop::pop_size_t Pop::get_num_demoted() const { + return num_demoted; +} + +Pop::pop_size_t Pop::get_num_migrated() const { + return num_migrated; +} + +Pop::pop_size_t Pop::get_pop_daily_change() const { + return Pop::get_num_promoted() - (Pop::get_num_demoted() + Pop::get_num_migrated()); +} + +PopType::PopType(const std::string_view new_identifier, colour_t new_colour, + strata_t new_strata, sprite_t new_sprite, + Pop::pop_size_t new_max_size, Pop::pop_size_t new_merge_max_size, + bool new_state_capital_only, bool new_demote_migrant, bool new_is_artisan, bool new_is_slave) + : HasIdentifierAndColour { new_identifier, new_colour, true }, + strata { new_strata }, + sprite { new_sprite }, + max_size { new_max_size }, + merge_max_size { new_merge_max_size }, + state_capital_only { new_state_capital_only }, + demote_migrant { new_demote_migrant }, + is_artisan { new_is_artisan }, + is_slave { new_is_slave } { + assert(sprite > 0); + assert(max_size > 0); + assert(merge_max_size > 0); +} + +PopType::sprite_t PopType::get_sprite() const { + return sprite; +} + +PopType::strata_t PopType::get_strata() const { + return strata; +} + +Pop::pop_size_t PopType::get_max_size() const { + return max_size; +} + +Pop::pop_size_t PopType::get_merge_max_size() const { + return merge_max_size; +} + +bool PopType::get_state_capital_only() const { + return state_capital_only; +} + +bool PopType::get_demote_migrant() const { + return demote_migrant; +} + +bool PopType::get_is_artisan() const { + return is_artisan; +} + +bool PopType::get_is_slave() const { + return is_slave; +} + +PopManager::PopManager() : pop_types { "pop types" } {} + +bool PopManager::add_pop_type(const std::string_view identifier, colour_t colour, PopType::strata_t strata, PopType::sprite_t sprite, + Pop::pop_size_t max_size, Pop::pop_size_t merge_max_size, bool state_capital_only, bool demote_migrant, bool is_artisan, bool is_slave) { + if (identifier.empty()) { + Logger::error("Invalid pop type identifier - empty!"); + return false; + } + if (colour > MAX_COLOUR_RGB) { + Logger::error("Invalid pop type colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + if (sprite <= 0) { + Logger::error("Invalid pop type sprite index for ", identifier, ": ", sprite); + return false; + } + if (max_size <= 0) { + Logger::error("Invalid pop type max size for ", identifier, ": ", max_size); + return false; + } + if (merge_max_size <= 0) { + Logger::error("Invalid pop type merge max size for ", identifier, ": ", merge_max_size); + return false; + } + return pop_types.add_item({ identifier, colour, strata, sprite, max_size, merge_max_size, state_capital_only, demote_migrant, is_artisan, is_slave }); +} + +bool PopManager::load_pop_type_file(const std::string_view filestem, ast::NodeCPtr root) { + + // TODO - pop type loading + + if (pop_types.empty()) + return add_pop_type("test_pop_type", 0xFF0000, PopType::strata_t::POOR, 1, 1, 1, false, false, false, false); + return true; +} + +bool PopManager::load_pop_into_province(Province& province, const std::string_view pop_type_identifier, ast::NodeCPtr pop_node) const { + static PopType const* type = get_pop_type_by_identifier("test_pop_type"); + Culture const* culture = nullptr; + Religion const* religion = nullptr; + Pop::pop_size_t size = 0; + + bool ret = expect_dictionary_keys( + "culture", ONE_EXACTLY, culture_manager.expect_culture(culture), + "religion", ONE_EXACTLY, religion_manager.expect_religion(religion), + "size", ONE_EXACTLY, expect_uint(assign_variable_callback_uint("pop size", size)), + "militancy", ZERO_OR_ONE, success_callback, + "rebel_type", ZERO_OR_ONE, success_callback + )(pop_node); + + if (type != nullptr && culture != nullptr && religion != nullptr && size > 0) { + ret &= province.add_pop({ *type, *culture, *religion, size }); + } else { + Logger::warning("Some pop arguments are invalid: province = ", province, ", type = ", type, ", culture = ", culture, ", religion = ", religion, ", size = ", size); + } + return ret; +} diff --git a/src/openvic/pop/Pop.hpp b/src/openvic-simulation/pop/Pop.hpp index db9633f..e70bc0b 100644 --- a/src/openvic/pop/Pop.hpp +++ b/src/openvic-simulation/pop/Pop.hpp @@ -1,8 +1,7 @@ #pragma once -#include "../Types.hpp" -#include "Culture.hpp" -#include "Religion.hpp" +#include "openvic-simulation/pop/Culture.hpp" +#include "openvic-simulation/pop/Religion.hpp" namespace OpenVic { @@ -21,7 +20,7 @@ namespace OpenVic { PopType const& type; Culture const& culture; Religion const& religion; - pop_size_t size, num_migrated, num_promoted, num_demoted, num_migrated; + pop_size_t size, num_promoted, num_demoted, num_migrated; Pop(PopType const& new_type, Culture const& new_culture, Religion const& new_religion, pop_size_t new_size); @@ -42,7 +41,7 @@ namespace OpenVic { }; /* REQUIREMENTS: - * POP-26 + * POP-15, POP-16, POP-17, POP-26 */ struct PopType : HasIdentifierAndColour { friend struct PopManager; @@ -82,18 +81,19 @@ namespace OpenVic { struct PopManager { private: IdentifierRegistry<PopType> pop_types; + + public: CultureManager culture_manager; ReligionManager religion_manager; - public: PopManager(); - return_t add_pop_type(const std::string_view identifier, colour_t new_colour, PopType::strata_t strata, PopType::sprite_t sprite, + bool add_pop_type(const std::string_view identifier, colour_t new_colour, PopType::strata_t strata, PopType::sprite_t sprite, Pop::pop_size_t max_size, Pop::pop_size_t merge_max_size, bool state_capital_only, bool demote_migrant, bool is_artisan, bool is_slave); - void lock_pop_types(); - PopType const* get_pop_type_by_identifier(const std::string_view identifier) const; + IDENTIFIER_REGISTRY_ACCESSORS(PopType, pop_type) - void generate_test_pops(Province& province) const; + bool load_pop_type_file(const std::string_view filestem, ast::NodeCPtr root); + bool load_pop_into_province(Province& province, const std::string_view pop_type_identifier, ast::NodeCPtr pop_node) const; }; } diff --git a/src/openvic-simulation/pop/Religion.cpp b/src/openvic-simulation/pop/Religion.cpp new file mode 100644 index 0000000..0652eb2 --- /dev/null +++ b/src/openvic-simulation/pop/Religion.cpp @@ -0,0 +1,112 @@ +#include "Religion.hpp" + +#include <cassert> + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +ReligionGroup::ReligionGroup(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} + +Religion::Religion(const std::string_view new_identifier, colour_t new_colour, + ReligionGroup const& new_group, icon_t new_icon, bool new_pagan) + : HasIdentifierAndColour { new_identifier, new_colour, true }, + group { new_group }, + icon { new_icon }, + pagan { new_pagan } { + assert(icon > 0); +} + +ReligionGroup const& Religion::get_group() const { + return group; +} + +Religion::icon_t Religion::get_icon() const { + return icon; +} + +bool Religion::get_pagan() const { + return pagan; +} + +ReligionManager::ReligionManager() + : religion_groups { "religion groups" }, + religions { "religions" } {} + +bool ReligionManager::add_religion_group(const std::string_view identifier) { + if (identifier.empty()) { + Logger::error("Invalid religion group identifier - empty!"); + return false; + } + return religion_groups.add_item({ identifier }); +} + +bool ReligionManager::add_religion(const std::string_view identifier, colour_t colour, ReligionGroup const* group, Religion::icon_t icon, bool pagan) { + if (!religion_groups.is_locked()) { + Logger::error("Cannot register religions until religion groups are locked!"); + return false; + } + if (identifier.empty()) { + Logger::error("Invalid religion identifier - empty!"); + return false; + } + if (group == nullptr) { + Logger::error("Null religion group for ", identifier); + return false; + } + if (colour > MAX_COLOUR_RGB) { + Logger::error("Invalid religion colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + if (icon <= 0) { + Logger::error("Invalid religion icon for ", identifier, ": ", icon); + return false; + } + return religions.add_item({ identifier, colour, *group, icon, pagan }); +} + +/* REQUIREMENTS: + * POP-286, POP-287, POP-288, POP-289, POP-290, POP-291, POP-292, + * POP-293, POP-294, POP-295, POP-296, POP-297, POP-298, POP-299 + */ +bool ReligionManager::load_religion_file(ast::NodeCPtr root) { + size_t total_expected_religions = 0; + bool ret = expect_dictionary_reserve_length( + religion_groups, + [this, &total_expected_religions](std::string_view key, ast::NodeCPtr value) -> bool { + bool ret = expect_list_and_length( + [&total_expected_religions](size_t size) -> size_t { + total_expected_religions += size; + return 0; + }, + success_callback + )(value); + ret &= add_religion_group(key); + return ret; + } + )(root); + lock_religion_groups(); + religions.reserve(religions.size() + total_expected_religions); + ret &= expect_dictionary( + [this](std::string_view religion_group_key, ast::NodeCPtr religion_group_value) -> bool { + ReligionGroup const* religion_group = get_religion_group_by_identifier(religion_group_key); + + return expect_dictionary( + [this, religion_group](std::string_view key, ast::NodeCPtr value) -> bool { + colour_t colour = NULL_COLOUR; + Religion::icon_t icon = 0; + bool pagan = false; + + bool ret = expect_dictionary_keys( + "icon", ONE_EXACTLY, expect_uint(assign_variable_callback_uint("religion icon", icon)), + "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), + "pagan", ZERO_OR_ONE, expect_bool(assign_variable_callback(pagan)) + )(value); + ret &= add_religion(key, colour, religion_group, icon, pagan); + return ret; + } + )(religion_group_value); + } + )(root); + lock_religions(); + return ret; +} diff --git a/src/openvic/pop/Religion.hpp b/src/openvic-simulation/pop/Religion.hpp index 4eb3e4c..8267659 100644 --- a/src/openvic/pop/Religion.hpp +++ b/src/openvic-simulation/pop/Religion.hpp @@ -1,6 +1,7 @@ #pragma once -#include "../Types.hpp" +#include "openvic-simulation/dataloader/NodeTools.hpp" +#include "openvic-simulation/types/IdentifierRegistry.hpp" namespace OpenVic { @@ -44,11 +45,12 @@ namespace OpenVic { public: ReligionManager(); - return_t add_religion_group(const std::string_view identifier); - void lock_religion_groups(); - ReligionGroup const* get_religion_group_by_identifier(const std::string_view identifier) const; - return_t add_religion(const std::string_view identifier, colour_t colour, ReligionGroup const* group, Religion::icon_t icon, bool pagan); - void lock_religions(); - Religion const* get_religion_by_identifier(const std::string_view identifier) const; + bool add_religion_group(const std::string_view identifier); + IDENTIFIER_REGISTRY_ACCESSORS(ReligionGroup, religion_group) + + bool add_religion(const std::string_view identifier, colour_t colour, ReligionGroup const* group, Religion::icon_t icon, bool pagan); + IDENTIFIER_REGISTRY_ACCESSORS(Religion, religion) + + bool load_religion_file(ast::NodeCPtr root); }; } diff --git a/src/openvic-simulation/types/Colour.hpp b/src/openvic-simulation/types/Colour.hpp new file mode 100644 index 0000000..01f3852 --- /dev/null +++ b/src/openvic-simulation/types/Colour.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include <algorithm> +#include <cstdint> +#include <iomanip> +#include <sstream> +#include <string> + +namespace OpenVic { + // Represents a 24-bit RGB integer OR a 32-bit ARGB integer + using colour_t = uint32_t; + /* When colour_t is used as an identifier, NULL_COLOUR is disallowed + * and should be reserved as an error value. + * When colour_t is used in a purely graphical context, NULL_COLOUR + * should be allowed. + */ + static constexpr colour_t NULL_COLOUR = 0, FULL_COLOUR = 0xFF, MAX_COLOUR_RGB = 0xFFFFFF; + constexpr colour_t float_to_colour_byte(float f, float min = 0.0f, float max = 1.0f) { + return static_cast<colour_t>(std::clamp(min + f * (max - min), min, max) * 255.0f); + } + constexpr colour_t fraction_to_colour_byte(int n, int d, float min = 0.0f, float max = 1.0f) { + return float_to_colour_byte(static_cast<float>(n) / static_cast<float>(d), min, max); + } + constexpr colour_t float_to_alpha_value(float a) { + return float_to_colour_byte(a) << 24; + } + constexpr float colour_byte_to_float(colour_t colour) { + return std::clamp(static_cast<float>(colour) / 255.0f, 0.0f, 1.0f); + } + + inline std::string colour_to_hex_string(colour_t colour) { + std::ostringstream stream; + stream << std::hex << std::setfill('0') << std::setw(6) << colour; + return stream.str(); + } +} diff --git a/src/openvic/Date.cpp b/src/openvic-simulation/types/Date.cpp index 7c37386..3449591 100644 --- a/src/openvic/Date.cpp +++ b/src/openvic-simulation/types/Date.cpp @@ -5,7 +5,8 @@ #include <cctype> #include <charconv> -#include "utility/Logger.hpp" +#include "openvic-simulation/utility/Logger.hpp" +#include "openvic-simulation/utility/StringUtils.hpp" using namespace OpenVic; @@ -55,12 +56,16 @@ Timespan::operator double() const { return days; } -Timespan::operator std::string() const { +std::string Timespan::to_string() const { return std::to_string(days); } +Timespan::operator std::string() const { + return to_string(); +} + std::ostream& OpenVic::operator<<(std::ostream& out, Timespan const& timespan) { - return out << static_cast<std::string>(timespan); + return out << timespan.to_string(); } Timespan Date::_dateToTimespan(year_t year, month_t month, day_t day) { @@ -146,81 +151,112 @@ Date Date::operator++(int) { return old; } -Date::operator std::string() const { +std::string Date::to_string() const { std::stringstream ss; ss << *this; return ss.str(); } +Date::operator std::string() const { + return to_string(); +} + std::ostream& OpenVic::operator<<(std::ostream& out, Date const& date) { - return out << static_cast<int>(date.getYear()) << '.' << static_cast<int>(date.getMonth()) << '.' << static_cast<int>(date.getDay()); + return out << static_cast<int>(date.getYear()) << Date::SEPARATOR_CHARACTER << static_cast<int>(date.getMonth()) << Date::SEPARATOR_CHARACTER << static_cast<int>(date.getDay()); } // Parsed from string of the form YYYY.MM.DD -Date Date::from_string(const std::string_view date) { - size_t first_pos = 0; - while (first_pos < date.length() && std::isdigit(date[first_pos])) { - first_pos++; +Date Date::from_string(char const* const str, char const* const end, bool* successful) { + if (successful != nullptr) *successful = true; + + year_t year = 0; + month_t month = 1; + day_t day = 1; + + if (str == nullptr || end <= str) { + Logger::error("Invalid string start/end pointers: ", static_cast<void const*>(str), " - ", static_cast<void const*>(end)); + if (successful != nullptr) *successful = false; + return { year, month, day }; } - if (first_pos == 0) { - Logger::error("Failed to find year digits in date: ", date); - return {}; + char const* year_end = str; + while (std::isdigit(*year_end) && ++year_end < end); + + if (year_end <= str) { + Logger::error("Failed to find year digits in date: ", std::string_view { str, static_cast<size_t>(end - str) }); + if (successful != nullptr) *successful = false; + return { year, month, day }; } - int val = 0; - char const* start = date.data(); - char const* end = start + first_pos; - std::from_chars_result result = std::from_chars(start, end, val); - if (result.ec != std::errc{} || result.ptr != end || val < 0 || val >= 1 << (8 * sizeof(year_t))) { - Logger::error("Failed to read year: ", date); - return {}; + bool sub_successful = false; + uint64_t val = StringUtils::string_to_uint64(str, year_end, &sub_successful, 10); + if (!sub_successful || val >= 1 << (8 * sizeof(year_t))) { + Logger::error("Failed to read year: ", std::string_view { str, static_cast<size_t>(end - str) }); + if (successful != nullptr) *successful = false; + return { year, month, day }; } - year_t year = val; - month_t month = 1; - day_t day = 1; - if (first_pos < date.length()) { - if (date[first_pos] == '.') { - size_t second_pos = ++first_pos; - while (second_pos < date.length() && std::isdigit(date[second_pos])) { - second_pos++; + year = val; + if (year_end < end) { + if (*year_end == SEPARATOR_CHARACTER) { + char const* const month_start = year_end + 1; + char const* month_end = month_start; + if (month_start < end) { + while (std::isdigit(*month_end) && ++month_end < end); } - if (first_pos == second_pos) { - Logger::error("Failed to find month digits in date: ", date); + if (month_start >= month_end) { + Logger::error("Failed to find month digits in date: ", std::string_view { str, static_cast<size_t>(end - str) }); + if (successful != nullptr) *successful = false; } else { - start = date.data() + first_pos; - end = date.data() + second_pos; - result = std::from_chars(start, end, val); - if (result.ec != std::errc{} || result.ptr != end || val < 1 || val > MONTHS_IN_YEAR) { - Logger::error("Failed to read month: ", date); + sub_successful = false; + val = StringUtils::string_to_uint64(month_start, month_end, &sub_successful, 10); + if (!sub_successful || val < 1 || val > MONTHS_IN_YEAR) { + Logger::error("Failed to read month: ", std::string_view { str, static_cast<size_t>(end - str) }); + if (successful != nullptr) *successful = false; } else { month = val; - if (second_pos < date.length()) { - if (date[second_pos] == '.') { - size_t third_pos = ++second_pos; - while (third_pos < date.length() && std::isdigit(date[third_pos])) { - third_pos++; + if (month_end < end) { + if (*month_end == SEPARATOR_CHARACTER) { + char const* const day_start = month_end + 1; + char const* day_end = day_start; + if (day_start < end) { + while (std::isdigit(*day_end) && ++day_end < end); } - if (second_pos == third_pos) { - Logger::error("Failed to find day digits in date: ", date); + if (day_start >= day_end) { + Logger::error("Failed to find day digits in date: ", std::string_view { str, static_cast<size_t>(end - str) }); + if (successful != nullptr) *successful = false; } else { - start = date.data() + second_pos; - end = date.data() + third_pos; - result = std::from_chars(start, end, val); - if (result.ec != std::errc{} || result.ptr != end || val < 1 || val > DAYS_IN_MONTH[month - 1]) { - Logger::error("Failed to read day: ", date); + sub_successful = false; + val = StringUtils::string_to_uint64(day_start, day_end, &sub_successful); + if (!sub_successful || val < 1 || val > DAYS_IN_MONTH[month - 1]) { + Logger::error("Failed to read day: ", std::string_view { str, static_cast<size_t>(end - str) }); + if (successful != nullptr) *successful = false; } else { day = val; - if (third_pos < date.length()) { - Logger::error("Unexpected string \"", date.substr(third_pos), "\" at the end of date ", date); + if (day_end < end) { + Logger::error("Unexpected string \"", std::string_view { day_end, static_cast<size_t>(end - day_end) }, "\" at the end of date ", std::string_view { str, static_cast<size_t>(end - str) }); + if (successful != nullptr) *successful = false; } } } - } else Logger::error("Unexpected character \"", date[second_pos], "\" in month of date ", date); + } else { + Logger::error("Unexpected character \"", *month_end, "\" in month of date ", std::string_view { str, static_cast<size_t>(end - str) }); + if (successful != nullptr) *successful = false; + } } } } - } else Logger::error("Unexpected character \"", date[first_pos], "\" in year of date ", date); + } else { + Logger::error("Unexpected character \"", *year_end, "\" in year of date ", std::string_view { str, static_cast<size_t>(end - str) }); + if (successful != nullptr) *successful = false; + } } - return _dateToTimespan(year, month, day); + return { year, month, day }; }; + +Date Date::from_string(char const* str, size_t length, bool* successful) { + return from_string(str, str + length, successful); +} + +Date Date::from_string(const std::string_view str, bool* successful) { + return from_string(str.data(), str.length(), successful); +} diff --git a/src/openvic/Date.hpp b/src/openvic-simulation/types/Date.hpp index 2994523..601f9dc 100644 --- a/src/openvic/Date.hpp +++ b/src/openvic-simulation/types/Date.hpp @@ -33,6 +33,7 @@ namespace OpenVic { explicit operator day_t() const; explicit operator double() const; + std::string to_string() const; explicit operator std::string() const; }; std::ostream& operator<<(std::ostream& out, Timespan const& timespan); @@ -50,6 +51,8 @@ namespace OpenVic { static Timespan::day_t const* DAYS_UP_TO_MONTH; static month_t const* MONTH_FROM_DAY_IN_YEAR; + static constexpr char SEPARATOR_CHARACTER = '.'; + private: // Number of days since Jan 1st, Year 0 Timespan timespan; @@ -82,9 +85,12 @@ namespace OpenVic { Date& operator++(); Date operator++(int); + std::string to_string() const; explicit operator std::string() const; // Parsed from string of the form YYYY.MM.DD - static Date from_string(const std::string_view date); + static Date from_string(char const* str, char const* end, bool* successful = nullptr); + static Date from_string(char const* str, size_t length, bool* successful = nullptr); + static Date from_string(const std::string_view str, bool* successful = nullptr); }; std::ostream& operator<<(std::ostream& out, Date const& date); } diff --git a/src/openvic/Types.cpp b/src/openvic-simulation/types/IdentifierRegistry.cpp index 4902ce3..e33bc29 100644 --- a/src/openvic/Types.cpp +++ b/src/openvic-simulation/types/IdentifierRegistry.cpp @@ -1,8 +1,6 @@ -#include "Types.hpp" +#include "IdentifierRegistry.hpp" #include <cassert> -#include <iomanip> -#include <sstream> using namespace OpenVic; @@ -15,23 +13,33 @@ std::string const& HasIdentifier::get_identifier() const { return identifier; } +std::ostream& OpenVic::operator<<(std::ostream& stream, HasIdentifier const& obj) { + return stream << obj.get_identifier(); +} + +std::ostream& OpenVic::operator<<(std::ostream& stream, HasIdentifier const* obj) { + return obj != nullptr ? stream << *obj : stream << "<NULL>"; +} + HasColour::HasColour(colour_t const new_colour, bool can_be_null) : colour(new_colour) { assert((can_be_null || colour != NULL_COLOUR) && colour <= MAX_COLOUR_RGB); } colour_t HasColour::get_colour() const { return colour; } -std::string HasColour::colour_to_hex_string(colour_t const colour) { - std::ostringstream stream; - stream << std::hex << std::setfill('0') << std::setw(6) << colour; - return stream.str(); -} - std::string HasColour::colour_to_hex_string() const { - return colour_to_hex_string(colour); + return OpenVic::colour_to_hex_string(colour); } HasIdentifierAndColour::HasIdentifierAndColour(const std::string_view new_identifier, const colour_t new_colour, bool can_be_null) : HasIdentifier { new_identifier }, HasColour { new_colour, can_be_null } {} + +distribution_t::value_type OpenVic::get_largest_item(distribution_t const& dist) { + const distribution_t::const_iterator result = std::max_element(dist.begin(), dist.end(), + [](distribution_t::value_type a, distribution_t::value_type b) -> bool { + return a.second < b.second; + }); + return result != dist.end() ? *result : distribution_t::value_type { nullptr, -1.0f }; +} diff --git a/src/openvic/Types.hpp b/src/openvic-simulation/types/IdentifierRegistry.hpp index dab455f..20eebb9 100644 --- a/src/openvic/Types.hpp +++ b/src/openvic-simulation/types/IdentifierRegistry.hpp @@ -1,46 +1,12 @@ #pragma once -#include <algorithm> -#include <cstdint> #include <map> -#include <unordered_map> #include <vector> -#include "utility/Logger.hpp" +#include "openvic-simulation/dataloader/NodeTools.hpp" +#include "openvic-simulation/utility/Logger.hpp" namespace OpenVic { - // Represents a 24-bit RGB integer OR a 32-bit ARGB integer - using colour_t = uint32_t; - /* When colour_t is used as an identifier, NULL_COLOUR is disallowed - * and should be reserved as an error value. - * When colour_t is used in a purely graphical context, NULL_COLOUR - * should be allowed. - */ - static constexpr colour_t NULL_COLOUR = 0, FULL_COLOUR = 0xFF, MAX_COLOUR_RGB = 0xFFFFFF; - constexpr colour_t float_to_colour_byte(float f, float min = 0.0f, float max = 1.0f) { - return static_cast<colour_t>(std::clamp(min + f * (max - min), min, max) * 255.0f); - } - constexpr colour_t fraction_to_colour_byte(int n, int d, float min = 0.0f, float max = 1.0f) { - return float_to_colour_byte(static_cast<float>(n) / static_cast<float>(d), min, max); - } - constexpr colour_t float_to_alpha_value(float a) { - return float_to_colour_byte(a) << 24; - } - constexpr float colour_byte_to_float(colour_t colour) { - return std::clamp(static_cast<float>(colour) / 255.0f, 0.0f, 1.0f); - } - - using index_t = uint16_t; - static constexpr index_t NULL_INDEX = 0, MAX_INDEX = 0xFFFF; - - // TODO: price_t must be changed to a fixed-point numeric type before multiplayer - using price_t = double; - static constexpr price_t NULL_PRICE = 0.0; - using return_t = bool; - - // This mirrors godot::Error, where `OK = 0` and `FAILED = 1`. - static constexpr return_t SUCCESS = false, FAILURE = true; - /* * Base class for objects with a non-empty string identifier, * uniquely named instances of which can be entered into an @@ -61,6 +27,9 @@ namespace OpenVic { std::string const& get_identifier() const; }; + std::ostream& operator<<(std::ostream& stream, HasIdentifier const& obj); + std::ostream& operator<<(std::ostream& stream, HasIdentifier const* obj); + /* * Base class for objects with associated colour information. */ @@ -78,7 +47,6 @@ namespace OpenVic { colour_t get_colour() const; std::string colour_to_hex_string() const; - static std::string colour_to_hex_string(colour_t const colour); }; /* @@ -96,7 +64,9 @@ namespace OpenVic { HasIdentifierAndColour& operator=(HasIdentifierAndColour&&) = delete; }; - using distribution_t = std::unordered_map<HasIdentifierAndColour const*, float>; + using distribution_t = std::map<HasIdentifierAndColour const*, float>; + + distribution_t::value_type get_largest_item(distribution_t const& dist); /* * Template for a list of objects with unique string identifiers that can @@ -104,71 +74,127 @@ namespace OpenVic { * the type of object that the registry will store, and the second part ensures * that HasIdentifier is a base class of T. */ - template<class T, typename std::enable_if<std::is_base_of<HasIdentifier, T>::value>::type* = nullptr> + template<typename T> + requires(std::derived_from<T, HasIdentifier>) class IdentifierRegistry { using identifier_index_map_t = std::map<std::string, size_t, std::less<void>>; const std::string name; + const bool log_lock; std::vector<T> items; bool locked = false; identifier_index_map_t identifier_index_map; public: - IdentifierRegistry(const std::string_view new_name) : name { new_name } {} - return_t add_item(T&& item) { + IdentifierRegistry(const std::string_view new_name, bool new_log_lock = true) + : name { new_name }, log_lock { new_log_lock } {} + + std::string const& get_name() const { + return name; + } + + bool add_item(T&& item) { if (locked) { Logger::error("Cannot add item to the ", name, " registry - locked!"); - return FAILURE; + return false; } T const* old_item = get_item_by_identifier(item.get_identifier()); if (old_item != nullptr) { Logger::error("Cannot add item to the ", name, " registry - an item with the identifier \"", item.get_identifier(), "\" already exists!"); - return FAILURE; + return false; } identifier_index_map[item.get_identifier()] = items.size(); items.push_back(std::move(item)); - return SUCCESS; + return true; } - void lock(bool log = true) { + + void lock() { if (locked) { Logger::error("Failed to lock ", name, " registry - already locked!"); } else { locked = true; - if (log) Logger::info("Locked ", name, " registry after registering ", get_item_count(), " items"); + if (log_lock) Logger::info("Locked ", name, " registry after registering ", size(), " items"); } } + bool is_locked() const { return locked; } + void reset() { identifier_index_map.clear(); items.clear(); locked = false; } - size_t get_item_count() const { + + size_t size() const { return items.size(); } + + bool empty() const { + return items.empty(); + } + + void reserve(size_t size) { + if (locked) { + Logger::error("Failed to reserve space for ", size, " items in ", name, " registry - already locked!"); + } else { + items.reserve(size); + } + } + T* get_item_by_identifier(const std::string_view identifier) { const identifier_index_map_t::const_iterator it = identifier_index_map.find(identifier); if (it != identifier_index_map.end()) return &items[it->second]; return nullptr; } + T const* get_item_by_identifier(const std::string_view identifier) const { const identifier_index_map_t::const_iterator it = identifier_index_map.find(identifier); if (it != identifier_index_map.end()) return &items[it->second]; return nullptr; } + T* get_item_by_index(size_t index) { return index < items.size() ? &items[index] : nullptr; } + T const* get_item_by_index(size_t index) const { return index < items.size() ? &items[index] : nullptr; } + std::vector<T>& get_items() { return items; } + std::vector<T> const& get_items() const { return items; } + + NodeTools::node_callback_t expect_item(T const*& ret) const { + return NodeTools::expect_identifier( + [this, &ret](std::string_view identifier) -> bool { + ret = get_item_by_identifier(identifier); + if (ret != nullptr) return true; + Logger::error("Invalid ", name, ": ", identifier); + return false; + } + ); + } }; + +#define IDENTIFIER_REGISTRY_ACCESSORS_CUSTOM_PLURAL(type, singular, plural) \ + void lock_##plural() { plural.lock(); } \ + type const* get_##singular##_by_index(size_t index) const { \ + return plural.get_item_by_index(index); } \ + type const* get_##singular##_by_identifier(const std::string_view identifier) const { \ + return plural.get_item_by_identifier(identifier); } \ + size_t get_##singular##_count() const { \ + return plural.size(); } \ + std::vector<type> const& get_##plural() const { \ + return plural.get_items(); } \ + NodeTools::node_callback_t expect_##singular(type const*& ret) const { \ + return plural.expect_item(ret); } + +#define IDENTIFIER_REGISTRY_ACCESSORS(type, name) IDENTIFIER_REGISTRY_ACCESSORS_CUSTOM_PLURAL(type, name, name##s) } diff --git a/src/openvic-simulation/types/fixed_point/FixedPoint.hpp b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp new file mode 100644 index 0000000..913f237 --- /dev/null +++ b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp @@ -0,0 +1,584 @@ +#pragma once + +#include <cerrno> +#include <cmath> +#include <cstdint> +#include <cstdlib> +#include <limits> +#include <string_view> + +#include "openvic-simulation/utility/Logger.hpp" +#include "openvic-simulation/utility/NumberUtils.hpp" +#include "openvic-simulation/utility/StringUtils.hpp" + +#include "FixedPointLUT.hpp" + +namespace OpenVic { + struct fixed_point_t { + static constexpr size_t SIZE = 8; + + static constexpr int32_t PRECISION = FPLUT::SIN_LUT_PRECISION; + static constexpr int64_t ONE = 1 << PRECISION; + + constexpr fixed_point_t() : value { 0 } {} + constexpr fixed_point_t(int64_t new_value) : value { new_value } {} + constexpr fixed_point_t(int32_t new_value) : value { static_cast<int64_t>(new_value) << PRECISION } {} + + // Trivial destructor + ~fixed_point_t() = default; + + static constexpr fixed_point_t max() { + return std::numeric_limits<int64_t>::max(); + } + + static constexpr fixed_point_t min() { + return std::numeric_limits<int64_t>::min(); + } + + static constexpr fixed_point_t usable_max() { + return static_cast<int64_t>(2147483648LL); + } + + static constexpr fixed_point_t usable_min() { + return -usable_max(); + } + + static constexpr fixed_point_t _0() { + return 0; + } + + static constexpr fixed_point_t _1() { + return 1; + } + + static constexpr fixed_point_t _2() { + return 2; + } + + static constexpr fixed_point_t _3() { + return 3; + } + + static constexpr fixed_point_t _4() { + return 4; + } + + static constexpr fixed_point_t _5() { + return 5; + } + + static constexpr fixed_point_t _6() { + return 6; + } + + static constexpr fixed_point_t _7() { + return 7; + } + + static constexpr fixed_point_t _8() { + return 8; + } + + static constexpr fixed_point_t _9() { + return 9; + } + + static constexpr fixed_point_t _10() { + return 10; + } + + static constexpr fixed_point_t _50() { + return 50; + } + + static constexpr fixed_point_t _100() { + return 100; + } + + static constexpr fixed_point_t _200() { + return 200; + } + + static constexpr fixed_point_t _0_01() { + return _1() / _100(); + } + + static constexpr fixed_point_t _0_02() { + return _0_01() * 2; + } + + static constexpr fixed_point_t _0_03() { + return _0_01() * 3; + } + + static constexpr fixed_point_t _0_04() { + return _0_01() * 4; + } + + static constexpr fixed_point_t _0_05() { + return _0_01() * 5; + } + + static constexpr fixed_point_t _0_10() { + return _1() / 10; + } + + static constexpr fixed_point_t _0_20() { + return _0_10() * 2; + } + + static constexpr fixed_point_t _0_25() { + return _1() / 4; + } + + static constexpr fixed_point_t _0_33() { + return _1() / 3; + } + + static constexpr fixed_point_t _0_50() { + return _1() / 2; + } + + static constexpr fixed_point_t _0_75() { + return _1() - _0_25(); + } + + static constexpr fixed_point_t _0_95() { + return _1() - _0_05(); + } + + static constexpr fixed_point_t _0_99() { + return _1() - _0_01(); + } + + static constexpr fixed_point_t _1_01() { + return _1() + _0_01(); + } + + static constexpr fixed_point_t _1_10() { + return _1() + _0_10(); + } + + static constexpr fixed_point_t _1_50() { + return _1() + _0_50(); + } + + static constexpr fixed_point_t minus_one() { + return -1; + } + + static constexpr fixed_point_t pi() { + return static_cast<int64_t>(205887LL); + } + + static constexpr fixed_point_t pi2() { + return pi() * 2; + } + + static constexpr fixed_point_t pi_quarter() { + return pi() / 4; + } + + static constexpr fixed_point_t pi_half() { + return pi() / 2; + } + + static constexpr fixed_point_t one_div_pi2() { + return 1 / pi2(); + } + + static constexpr fixed_point_t deg2rad() { + return static_cast<int64_t>(1143LL); + } + + static constexpr fixed_point_t rad2deg() { + return static_cast<int64_t>(3754936LL); + } + + static constexpr fixed_point_t e() { + return static_cast<int64_t>(178145LL); + } + + constexpr bool is_negative() const { + return value < 0; + } + + constexpr fixed_point_t abs() const { + return !is_negative() ? value : -value; + } + + // Doesn't account for sign, so -n.abc -> 1 - 0.abc + constexpr fixed_point_t get_frac() const { + return value & (ONE - 1); + } + + constexpr int64_t to_int64_t() const { + return value >> PRECISION; + } + + constexpr void set_raw_value(int64_t value) { + this->value = value; + } + + constexpr int64_t get_raw_value() const { + return value; + } + + constexpr int32_t to_int32_t() const { + return static_cast<int32_t>(to_int64_t()); + } + + constexpr float to_float() const { + return value / static_cast<float>(ONE); + } + + constexpr float to_float_rounded() const { + return static_cast<float>(NumberUtils::round_to_int64((value / static_cast<float>(ONE)) * 100000.0f)) / 100000.0f; + } + + constexpr double to_double() const { + return value / static_cast<double>(ONE); + } + + constexpr float to_double_rounded() const { + return NumberUtils::round_to_int64((value / static_cast<double>(ONE)) * 100000.0) / 100000.0; + } + + std::string to_string() const { + fixed_point_t val = abs(); + std::string str = std::to_string(val.to_int64_t()) + "."; + if (is_negative()) str = "-" + str; + val = val.get_frac(); + do { + val *= 10; + str.push_back('0' + static_cast<char>(val.to_int64_t())); + val = val.get_frac(); + } while (val > 0); + return str; + } + + // Deterministic + static constexpr fixed_point_t parse_raw(int64_t value) { + return value; + } + + // Deterministic + static constexpr fixed_point_t parse(int64_t value) { + return value << PRECISION; + } + + // Deterministic + static constexpr fixed_point_t parse(char const* str, char const* const end, bool* successful = nullptr) { + if (successful != nullptr) *successful = false; + + if (str == nullptr || str >= end) { + return _0(); + } + + bool negative = false; + + if (*str == '-') { + negative = true; + ++str; + if (str == end) return _0(); + } + + char const* dot_pointer = str; + while (*dot_pointer != '.' && ++dot_pointer != end); + + if (dot_pointer == str && dot_pointer + 1 == end) { + // Invalid: ".", "+." or "-." + return _0(); + } + + fixed_point_t result = _0(); + if (successful != nullptr) *successful = true; + + if (dot_pointer != str) { + // Non-empty integer part + bool int_successful = false; + result += parse_integer(str, dot_pointer, &int_successful); + if (!int_successful && successful != nullptr) *successful = false; + } + + if (dot_pointer + 1 < end) { + // Non-empty fractional part + bool frac_successful = false; + result += parse_fraction(dot_pointer + 1, end, &frac_successful); + if (!frac_successful && successful != nullptr) *successful = false; + } + + return negative ? -result : result; + } + + static constexpr fixed_point_t parse(char const* str, size_t length, bool* successful = nullptr) { + return parse(str, str + length, successful); + } + + static fixed_point_t parse(const std::string_view str, bool* successful = nullptr) { + return parse(str.data(), str.length(), successful); + } + + // Not Deterministic + static constexpr fixed_point_t parse_unsafe(float value) { + return static_cast<int64_t>(value * ONE + 0.5f * (value < 0 ? -1 : 1)); + } + + // Not Deterministic + static fixed_point_t parse_unsafe(char const* value) { + char* endpointer; + double double_value = std::strtod(value, &endpointer); + + if (*endpointer != '\0') { + Logger::error("Unsafe fixed point parse failed to parse the end of a string: \"", endpointer, "\""); + } + + int64_t integer_value = static_cast<long>(double_value * ONE + 0.5 * (double_value < 0 ? -1 : 1)); + + return integer_value; + } + + constexpr operator int32_t() const { + return to_int32_t(); + } + + constexpr operator int64_t() const { + return to_int64_t(); + } + + constexpr operator float() const { + return to_float(); + } + + constexpr operator double() const { + return to_double(); + } + + operator std::string() const { + return to_string(); + } + + friend std::ostream& operator<<(std::ostream& stream, fixed_point_t const& obj) { + return stream << obj.to_string(); + } + + constexpr friend fixed_point_t operator-(fixed_point_t const& obj) { + return -obj.value; + } + + constexpr friend fixed_point_t operator+(fixed_point_t const& obj) { + return +obj.value; + } + + constexpr friend fixed_point_t operator+(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value + rhs.value; + } + + constexpr friend fixed_point_t operator+(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value + (static_cast<int64_t>(rhs) << PRECISION); + } + + constexpr friend fixed_point_t operator+(int32_t const& lhs, fixed_point_t const& rhs) { + return (static_cast<int64_t>(lhs) << PRECISION) + rhs.value; + } + + constexpr fixed_point_t operator+=(fixed_point_t const& obj) { + value += obj.value; + return *this; + } + + constexpr fixed_point_t operator+=(int32_t const& obj) { + value += (static_cast<int64_t>(obj) << PRECISION); + return *this; + } + + constexpr friend fixed_point_t operator-(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value - rhs.value; + } + + constexpr friend fixed_point_t operator-(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value - (static_cast<int64_t>(rhs) << PRECISION); + } + + constexpr friend fixed_point_t operator-(int32_t const& lhs, fixed_point_t const& rhs) { + return (static_cast<int64_t>(lhs) << PRECISION) - rhs.value; + } + + constexpr fixed_point_t operator-=(fixed_point_t const& obj) { + value -= obj.value; + return *this; + } + + constexpr fixed_point_t operator-=(int32_t const& obj) { + value -= (static_cast<int64_t>(obj) << PRECISION); + return *this; + } + + constexpr friend fixed_point_t operator*(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value * rhs.value >> PRECISION; + } + + constexpr friend fixed_point_t operator*(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value * rhs; + } + + constexpr friend fixed_point_t operator*(int32_t const& lhs, fixed_point_t const& rhs) { + return lhs * rhs.value; + } + + constexpr fixed_point_t operator*=(fixed_point_t const& obj) { + value *= obj.value >> PRECISION; + return *this; + } + + constexpr fixed_point_t operator*=(int32_t const& obj) { + value *= obj; + return *this; + } + + constexpr friend fixed_point_t operator/(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return (lhs.value << PRECISION) / rhs.value; + } + + constexpr friend fixed_point_t operator/(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value / rhs; + } + + constexpr friend fixed_point_t operator/(int32_t const& lhs, fixed_point_t const& rhs) { + return (static_cast<int64_t>(lhs) << (2 * PRECISION)) / rhs.value; + } + + constexpr fixed_point_t operator/=(fixed_point_t const& obj) { + value = (value << PRECISION) / obj.value; + return *this; + } + + constexpr fixed_point_t operator/=(int32_t const& obj) { + value /= obj; + return *this; + } + + constexpr friend fixed_point_t operator%(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value % rhs.value; + } + + constexpr friend fixed_point_t operator%(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value % (static_cast<int64_t>(rhs) << PRECISION); + } + + constexpr friend fixed_point_t operator%(int32_t const& lhs, fixed_point_t const& rhs) { + return (static_cast<int64_t>(lhs) << PRECISION) % rhs.value; + } + + constexpr fixed_point_t operator%=(fixed_point_t const& obj) { + value %= obj.value; + return *this; + } + + constexpr fixed_point_t operator%=(int32_t const& obj) { + value %= (static_cast<int64_t>(obj) << PRECISION); + return *this; + } + + constexpr friend bool operator<(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value < rhs.value; + } + + constexpr friend bool operator<(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value < static_cast<int64_t>(rhs) << PRECISION; + } + + constexpr friend bool operator<(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast<int64_t>(lhs) << PRECISION < rhs.value; + } + + constexpr friend bool operator<=(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value <= rhs.value; + } + + constexpr friend bool operator<=(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value <= static_cast<int64_t>(rhs) << PRECISION; + } + + constexpr friend bool operator<=(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast<int64_t>(lhs) << PRECISION <= rhs.value; + } + + constexpr friend bool operator>(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value > rhs.value; + } + + constexpr friend bool operator>(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value > static_cast<int64_t>(rhs) << PRECISION; + } + + constexpr friend bool operator>(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast<int64_t>(lhs) << PRECISION > rhs.value; + } + + constexpr friend bool operator>=(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value >= rhs.value; + } + + constexpr friend bool operator>=(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value >= static_cast<int64_t>(rhs) << PRECISION; + } + + constexpr friend bool operator>=(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast<int64_t>(lhs) << PRECISION >= rhs.value; + } + + constexpr friend bool operator==(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value == rhs.value; + } + + constexpr friend bool operator==(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value == static_cast<int64_t>(rhs) << PRECISION; + } + + constexpr friend bool operator==(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast<int64_t>(lhs) << PRECISION == rhs.value; + } + + constexpr friend bool operator!=(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value != rhs.value; + } + + constexpr friend bool operator!=(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value != static_cast<int64_t>(rhs) << PRECISION; + } + + constexpr friend bool operator!=(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast<int64_t>(lhs) << PRECISION != rhs.value; + } + + private: + int64_t value; + + static constexpr fixed_point_t parse_integer(char const* str, char const* const end, bool* successful) { + int64_t parsed_value = StringUtils::string_to_int64(str, end, successful, 10); + return parse(parsed_value); + } + + static constexpr fixed_point_t parse_fraction(char const* str, char const* end, bool* successful) { + char const* const read_end = str + PRECISION; + if (read_end < end) end = read_end; + uint64_t parsed_value = StringUtils::string_to_uint64(str, end, successful, 10); + while (end++ < read_end) { + parsed_value *= 10; + } + uint64_t decimal = NumberUtils::pow(static_cast<uint64_t>(10), PRECISION); + int64_t ret = 0; + for (int i = PRECISION - 1; i >= 0; --i) { + decimal >>= 1; + if (parsed_value > decimal) { + parsed_value -= decimal; + ret |= 1 << i; + } + } + return ret; + } + }; + + static_assert(sizeof(fixed_point_t) == fixed_point_t::SIZE, "fixed_point_t is not 8 bytes"); +} diff --git a/src/openvic-simulation/types/fixed_point/FixedPointLUT.hpp b/src/openvic-simulation/types/fixed_point/FixedPointLUT.hpp new file mode 100644 index 0000000..45cb639 --- /dev/null +++ b/src/openvic-simulation/types/fixed_point/FixedPointLUT.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include <array> +#include <cmath> +#include <cstddef> +#include <cstdint> +#include <numbers> +#include <utility> + +namespace OpenVic::FPLUT { + +#include "FixedPointLUT_sin.hpp" + + constexpr int32_t SHIFT = SIN_LUT_PRECISION - SIN_LUT_COUNT_LOG2; + + constexpr int64_t sin(int64_t value) { + int sign = 1; + if (value < 0) { + value = -value; + sign = -1; + } + + int index = value >> SHIFT; + int64_t a = SIN_LUT[index]; + int64_t b = SIN_LUT[index + 1]; + int64_t fraction = (value - (index << SHIFT)) << SIN_LUT_COUNT_LOG2; + int64_t result = a + (((b - a) * fraction) >> SIN_LUT_PRECISION); + return result * sign; + } +} diff --git a/src/openvic-simulation/types/fixed_point/FixedPointLUT_sin.hpp b/src/openvic-simulation/types/fixed_point/FixedPointLUT_sin.hpp new file mode 100644 index 0000000..0c75efe --- /dev/null +++ b/src/openvic-simulation/types/fixed_point/FixedPointLUT_sin.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include <cstdint> + +static constexpr int32_t SIN_LUT_PRECISION = 16; +static constexpr int32_t SIN_LUT_COUNT_LOG2 = 9; + +static constexpr int64_t SIN_LUT[(1 << SIN_LUT_COUNT_LOG2) + 1] = { + 0, 804, 1608, 2412, 3216, 4019, 4821, 5623, 6424, 7224, 8022, 8820, 9616, 10411, 11204, 11996, + 12785, 13573, 14359, 15143, 15924, 16703, 17479, 18253, 19024, 19792, 20557, 21320, 22078, 22834, 23586, 24335, + 25080, 25821, 26558, 27291, 28020, 28745, 29466, 30182, 30893, 31600, 32303, 33000, 33692, 34380, 35062, 35738, + 36410, 37076, 37736, 38391, 39040, 39683, 40320, 40951, 41576, 42194, 42806, 43412, 44011, 44604, 45190, 45769, + 46341, 46906, 47464, 48015, 48559, 49095, 49624, 50146, 50660, 51166, 51665, 52156, 52639, 53114, 53581, 54040, + 54491, 54934, 55368, 55794, 56212, 56621, 57022, 57414, 57798, 58172, 58538, 58896, 59244, 59583, 59914, 60235, + 60547, 60851, 61145, 61429, 61705, 61971, 62228, 62476, 62714, 62943, 63162, 63372, 63572, 63763, 63944, 64115, + 64277, 64429, 64571, 64704, 64827, 64940, 65043, 65137, 65220, 65294, 65358, 65413, 65457, 65492, 65516, 65531, + 65536, 65531, 65516, 65492, 65457, 65413, 65358, 65294, 65220, 65137, 65043, 64940, 64827, 64704, 64571, 64429, + 64277, 64115, 63944, 63763, 63572, 63372, 63162, 62943, 62714, 62476, 62228, 61971, 61705, 61429, 61145, 60851, + 60547, 60235, 59914, 59583, 59244, 58896, 58538, 58172, 57798, 57414, 57022, 56621, 56212, 55794, 55368, 54934, + 54491, 54040, 53581, 53114, 52639, 52156, 51665, 51166, 50660, 50146, 49624, 49095, 48559, 48015, 47464, 46906, + 46341, 45769, 45190, 44604, 44011, 43412, 42806, 42194, 41576, 40951, 40320, 39683, 39040, 38391, 37736, 37076, + 36410, 35738, 35062, 34380, 33692, 33000, 32303, 31600, 30893, 30182, 29466, 28745, 28020, 27291, 26558, 25821, + 25080, 24335, 23586, 22834, 22078, 21320, 20557, 19792, 19024, 18253, 17479, 16703, 15924, 15143, 14359, 13573, + 12785, 11996, 11204, 10411, 9616, 8820, 8022, 7224, 6424, 5623, 4821, 4019, 3216, 2412, 1608, 804, + 0, -804, -1608, -2412, -3216, -4019, -4821, -5623, -6424, -7224, -8022, -8820, -9616, -10411, -11204, -11996, + -12785, -13573, -14359, -15143, -15924, -16703, -17479, -18253, -19024, -19792, -20557, -21320, -22078, -22834, -23586, -24335, + -25080, -25821, -26558, -27291, -28020, -28745, -29466, -30182, -30893, -31600, -32303, -33000, -33692, -34380, -35062, -35738, + -36410, -37076, -37736, -38391, -39040, -39683, -40320, -40951, -41576, -42194, -42806, -43412, -44011, -44604, -45190, -45769, + -46341, -46906, -47464, -48015, -48559, -49095, -49624, -50146, -50660, -51166, -51665, -52156, -52639, -53114, -53581, -54040, + -54491, -54934, -55368, -55794, -56212, -56621, -57022, -57414, -57798, -58172, -58538, -58896, -59244, -59583, -59914, -60235, + -60547, -60851, -61145, -61429, -61705, -61971, -62228, -62476, -62714, -62943, -63162, -63372, -63572, -63763, -63944, -64115, + -64277, -64429, -64571, -64704, -64827, -64940, -65043, -65137, -65220, -65294, -65358, -65413, -65457, -65492, -65516, -65531, + -65536, -65531, -65516, -65492, -65457, -65413, -65358, -65294, -65220, -65137, -65043, -64940, -64827, -64704, -64571, -64429, + -64277, -64115, -63944, -63763, -63572, -63372, -63162, -62943, -62714, -62476, -62228, -61971, -61705, -61429, -61145, -60851, + -60547, -60235, -59914, -59583, -59244, -58896, -58538, -58172, -57798, -57414, -57022, -56621, -56212, -55794, -55368, -54934, + -54491, -54040, -53581, -53114, -52639, -52156, -51665, -51166, -50660, -50146, -49624, -49095, -48559, -48015, -47464, -46906, + -46341, -45769, -45190, -44604, -44011, -43412, -42806, -42194, -41576, -40951, -40320, -39683, -39040, -38391, -37736, -37076, + -36410, -35738, -35062, -34380, -33692, -33000, -32303, -31600, -30893, -30182, -29466, -28745, -28020, -27291, -26558, -25821, + -25080, -24335, -23586, -22834, -22078, -21320, -20557, -19792, -19024, -18253, -17479, -16703, -15924, -15143, -14359, -13573, + -12785, -11996, -11204, -10411, -9616, -8820, -8022, -7224, -6424, -5623, -4821, -4019, -3216, -2412, -1608, -804, + 0 +}; diff --git a/src/openvic-simulation/types/fixed_point/FixedPointMath.hpp b/src/openvic-simulation/types/fixed_point/FixedPointMath.hpp new file mode 100644 index 0000000..c0b5f42 --- /dev/null +++ b/src/openvic-simulation/types/fixed_point/FixedPointMath.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "FixedPoint.hpp" + +namespace OpenVic::FPMath { + constexpr fixed_point_t sin(fixed_point_t number) { + number %= fixed_point_t::pi2(); + number *= fixed_point_t::one_div_pi2(); + return FPLUT::sin(number.get_raw_value()); + } +} diff --git a/src/openvic-simulation/types/fixed_point/lut_generator/lut_generator.py b/src/openvic-simulation/types/fixed_point/lut_generator/lut_generator.py new file mode 100644 index 0000000..8ae7a32 --- /dev/null +++ b/src/openvic-simulation/types/fixed_point/lut_generator/lut_generator.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +from math import pi, sin +from argparse import ArgumentParser +from sys import exit + +def generate_sin_lut(precision : int, count_log2 : int): + one = 1 << precision + count = 1 << count_log2 + + SinLut = [] + + for i in range(count): + angle = 2 * pi * i / count + + sin_value = sin(angle) + moved_sin = sin_value * one + rounded_sin = int(moved_sin + 0.5) if moved_sin > 0 else int(moved_sin - 0.5) + SinLut.append(rounded_sin) + + SinLut.append(SinLut[0]) + + output = [ + "#pragma once", + "", + "#include <cstdint>", + "", + f"static constexpr int32_t SIN_LUT_PRECISION = {precision};", + f"static constexpr int32_t SIN_LUT_COUNT_LOG2 = {count_log2};", + "", + "static constexpr int64_t SIN_LUT[(1 << SIN_LUT_COUNT_LOG2) + 1] = {" + ] + + VALS_PER_LINE = 16 + + lines = [SinLut[i : i + VALS_PER_LINE] for i in range(0, len(SinLut), VALS_PER_LINE)] + + for line in lines: + output.append("\t" + ", ".join(str(value) for value in line) + ",") + + output[-1] = output[-1][:-1] # Remove the last comma + output += ["};", ""] + + cpp_code = "\n".join(output) + + with open(f"FixedPointLUT_sin.hpp", "w", newline="\n") as file: + file.write(cpp_code) + +PRECISION = 16 +COUNT = 9 + +if __name__ == "__main__": + + parser = ArgumentParser(prog="Fixed Point LUT Generator", description="Fixed-Point number Look-Up Table generator") + parser.add_argument("-p", "--precision", type=int, default=PRECISION, choices=range(1, 65), help="The number of bits after the point (fractional bits)") + parser.add_argument("-c", "--count", type=int, default=COUNT, choices=range(1, 65), help="The base 2 log of the number of values in the look-up table (must be <= precision)") + args = parser.parse_args() + + if args.precision < args.count: + print("ERROR: invalid count ", args.count, " - can't be greater than precision (", args.precision, ")") + exit(-1) + else: + generate_sin_lut(args.precision, args.count) + exit(0) diff --git a/src/openvic/utility/BMP.cpp b/src/openvic-simulation/utility/BMP.cpp index 9a90d68..531870b 100644 --- a/src/openvic/utility/BMP.cpp +++ b/src/openvic-simulation/utility/BMP.cpp @@ -3,7 +3,7 @@ #include <cstring> #include <set> -#include "Logger.hpp" +#include "openvic-simulation/utility/Logger.hpp" using namespace OpenVic; @@ -11,57 +11,58 @@ BMP::~BMP() { close(); } -return_t BMP::open(char const* filepath) { +bool BMP::open(char const* filepath) { reset(); errno = 0; file = fopen(filepath, "rb"); if (file == nullptr || errno != 0) { Logger::error("Failed to open BMP file \"", filepath, "\" (errno = ", errno, ")"); file = nullptr; - return FAILURE; + return false; } - return SUCCESS; + return true; } -return_t BMP::read_header() { +bool BMP::read_header() { if (header_validated) { Logger::error("BMP header already validated!"); - return FAILURE; + return false; } if (file == nullptr) { Logger::error("Cannot read BMP header before opening a file"); - return FAILURE; + return false; } if (fseek(file, 0, SEEK_SET) != 0) { Logger::error("Failed to move to the beginning of the BMP file!"); - return FAILURE; + return false; } if (fread(&header, sizeof(header), 1, file) != 1) { Logger::error("Failed to read BMP header!"); - return FAILURE; + return false; } - return_t ret = SUCCESS; + + header_validated = true; // Validate constants static constexpr uint16_t BMP_SIGNATURE = 0x4d42; if (header.signature != BMP_SIGNATURE) { Logger::error("Invalid BMP signature: ", header.signature, " (must be ", BMP_SIGNATURE, ")"); - ret = FAILURE; + header_validated = false; } static constexpr uint32_t DIB_HEADER_SIZE = 40; if (header.dib_header_size != DIB_HEADER_SIZE) { Logger::error("Invalid BMP DIB header size: ", header.dib_header_size, " (must be ", DIB_HEADER_SIZE, ")"); - ret = FAILURE; + header_validated = false; } static constexpr uint16_t NUM_PLANES = 1; if (header.num_planes != NUM_PLANES) { Logger::error("Invalid BMP plane count: ", header.num_planes, " (must be ", NUM_PLANES, ")"); - ret = FAILURE; + header_validated = false; } static constexpr uint16_t COMPRESSION = 0; // Only support uncompressed BMPs if (header.compression != COMPRESSION) { Logger::error("Invalid BMP compression method: ", header.compression, " (must be ", COMPRESSION, ")"); - ret = FAILURE; + header_validated = false; } // Validate sizes and dimensions @@ -69,17 +70,17 @@ return_t BMP::read_header() { if (header.file_size != header.offset + header.image_size_bytes) { Logger::error("Invalid BMP memory sizes: file size = ", header.file_size, " != ", header.offset + header.image_size_bytes, " = ", header.offset, " + ", header.image_size_bytes, " = image data offset + image data size"); - ret = FAILURE; + header_validated = false; } // TODO - support negative widths (i.e. horizontal flip) if (header.width_px <= 0) { Logger::error("Invalid BMP width: ", header.width_px, " (must be positive)"); - ret = FAILURE; + header_validated = false; } // TODO - support negative heights (i.e. vertical flip) if (header.height_px <= 0) { Logger::error("Invalid BMP height: ", header.height_px, " (must be positive)"); - ret = FAILURE; + header_validated = false; } // TODO - validate x_resolution_ppm // TODO - validate y_resolution_ppm @@ -90,14 +91,14 @@ return_t BMP::read_header() { static const std::set<uint16_t> BITS_PER_PIXEL { VALID_BITS_PER_PIXEL }; if (!BITS_PER_PIXEL.contains(header.bits_per_pixel)) { Logger::error("Invalid BMP bits per pixel: ", header.bits_per_pixel, " (must be one of " STR(VALID_BITS_PER_PIXEL) ")"); - ret = FAILURE; + header_validated = false; } #undef VALID_BITS_PER_PIXEL #undef STR static constexpr uint16_t PALETTE_BITS_PER_PIXEL_LIMIT = 8; if (header.num_colours != 0 && header.bits_per_pixel > PALETTE_BITS_PER_PIXEL_LIMIT) { Logger::error("Invalid BMP palette size: ", header.num_colours, " (should be 0 as bits per pixel is ", header.bits_per_pixel, " > 8)"); - ret = FAILURE; + header_validated = false; } // TODO - validate important_colours @@ -109,37 +110,36 @@ return_t BMP::read_header() { const uint32_t expected_offset = palette_size * PALETTE_COLOUR_SIZE + sizeof(header); if (header.offset != expected_offset) { Logger::error("Invalid BMP image data offset: ", header.offset, " (should be ", expected_offset, ")"); - ret = FAILURE; + header_validated = false; } - header_validated = ret == SUCCESS; - return ret; + return header_validated; } -return_t BMP::read_palette() { +bool BMP::read_palette() { if (file == nullptr) { Logger::error("Cannot read BMP palette before opening a file"); - return FAILURE; + return false; } if (!header_validated) { Logger::error("Cannot read palette before BMP header is validated!"); - return FAILURE; + return false; } if (palette_size == 0) { Logger::error("Cannot read BMP palette - header indicates this file doesn't have one"); - return FAILURE; + return false; } if (fseek(file, sizeof(header), SEEK_SET) != 0) { Logger::error("Failed to move to the palette in the BMP file!"); - return FAILURE; + return false; } palette.resize(palette_size); if (fread(palette.data(), palette_size * PALETTE_COLOUR_SIZE, 1, file) != 1) { Logger::error("Failed to read BMP header!"); palette.clear(); - return FAILURE; + return false; } - return SUCCESS; + return true; } void BMP::close() { diff --git a/src/openvic/utility/BMP.hpp b/src/openvic-simulation/utility/BMP.hpp index c6f5815..f04b41a 100644 --- a/src/openvic/utility/BMP.hpp +++ b/src/openvic-simulation/utility/BMP.hpp @@ -1,6 +1,9 @@ #pragma once -#include "../Types.hpp" +#include <cstdio> +#include <vector> + +#include "openvic-simulation/types/Colour.hpp" namespace OpenVic { class BMP { @@ -37,9 +40,9 @@ namespace OpenVic { BMP() = default; ~BMP(); - return_t open(char const* filepath); - return_t read_header(); - return_t read_palette(); + bool open(char const* filepath); + bool read_header(); + bool read_palette(); void close(); void reset(); diff --git a/src/openvic/utility/Logger.cpp b/src/openvic-simulation/utility/Logger.cpp index bf9a67c..fca08a5 100644 --- a/src/openvic/utility/Logger.cpp +++ b/src/openvic-simulation/utility/Logger.cpp @@ -6,6 +6,8 @@ using namespace OpenVic; Logger::log_func_t Logger::info_func {}; Logger::log_queue_t Logger::info_queue {}; +Logger::log_func_t Logger::warning_func {}; +Logger::log_queue_t Logger::warning_queue {}; Logger::log_func_t Logger::error_func {}; Logger::log_queue_t Logger::error_queue {}; diff --git a/src/openvic/utility/Logger.hpp b/src/openvic-simulation/utility/Logger.hpp index 417aba7..f9ebd5d 100644 --- a/src/openvic/utility/Logger.hpp +++ b/src/openvic-simulation/utility/Logger.hpp @@ -80,6 +80,7 @@ namespace OpenVic { name(Ts&&...) -> name<Ts...>; LOG_FUNC(info) + LOG_FUNC(warning) LOG_FUNC(error) #undef LOG_FUNC diff --git a/src/openvic-simulation/utility/NumberUtils.hpp b/src/openvic-simulation/utility/NumberUtils.hpp new file mode 100644 index 0000000..6211772 --- /dev/null +++ b/src/openvic-simulation/utility/NumberUtils.hpp @@ -0,0 +1,27 @@ +#include <cstdint> + +namespace OpenVic::NumberUtils { + constexpr int64_t round_to_int64(float num) { + return (num > 0.0f) ? (num + 0.5f) : (num - 0.5f); + } + + constexpr int64_t round_to_int64(double num) { + return (num > 0.0) ? (num + 0.5) : (num - 0.5); + } + + constexpr uint64_t pow(uint64_t base, size_t exponent) { + uint64_t ret = 1; + while (exponent-- > 0) { + ret *= base; + } + return ret; + } + + constexpr int64_t pow(int64_t base, size_t exponent) { + int64_t ret = 1; + while (exponent-- > 0) { + ret *= base; + } + return ret; + } +} diff --git a/src/openvic-simulation/utility/StringUtils.hpp b/src/openvic-simulation/utility/StringUtils.hpp new file mode 100644 index 0000000..97efbed --- /dev/null +++ b/src/openvic-simulation/utility/StringUtils.hpp @@ -0,0 +1,127 @@ +#include <cstdint> +#include <limits> +#include <string_view> + +namespace OpenVic::StringUtils { + /* The constexpr function 'string_to_uint64' will convert a string into a uint64_t integer value. + * The function takes four parameters: the input string (as a pair of pointers marking the start and + * end of the string), a bool pointer for reporting success, and the base for numerical conversion. + * The base parameter defaults to 10 (decimal), but it can be any value between 2 and 36. If the base + * given is 0, it will be set to 16 if the string starts with "0x" or "0X", otherwise 8 if the string + * still starts with "0", otherwise 10. The success bool pointer parameter is used to report whether + * or not conversion was successful. It can be nullptr if this information is not needed. + */ + constexpr uint64_t string_to_uint64(char const* str, const char* const end, bool* successful = nullptr, int base = 10) { + if (successful != nullptr) *successful = false; + + // Base value should be between 2 and 36. If it's not, return 0 as an invalid case. + if (str == nullptr || end <= str || base < 0 || base == 1 || base > 36) + return 0; + + // The result of the conversion will be stored in this variable. + uint64_t result = 0; + + // If base is zero, base is determined by the string prefix. + if (base == 0) { + if (*str == '0') { + if (str + 1 != end && (str[1] == 'x' || str[1] == 'X')) { + base = 16; // Hexadecimal. + str += 2; // Skip '0x' or '0X' + if (str == end) return 0; + } else { + base = 8; // Octal. + } + } else { + base = 10; // Decimal. + } + } else if (base == 16) { + // If base is 16 and string starts with '0x' or '0X', skip these characters. + if (*str == '0' && str + 1 != end && (str[1] == 'x' || str[1] == 'X')) { + str += 2; + if (str == end) return 0; + } + } + + // Convert the number in the string. + for (; str != end; ++str) { + int digit; + if (*str >= '0' && *str <= '9') { + digit = *str - '0'; // Calculate digit value for '0'-'9'. + } else if (*str >= 'a' && *str <= 'z') { + digit = *str - 'a' + 10; // Calculate digit value for 'a'-'z'. + } else if (*str >= 'A' && *str <= 'Z') { + digit = *str - 'A' + 10; // Calculate digit value for 'A'-'Z'. + } else { + break; // Stop conversion if current character is not a digit. + } + + if (digit >= base) { + break; // Stop conversion if current digit is greater than or equal to the base. + } + + // Check for overflow on multiplication + if (result > std::numeric_limits<uint64_t>::max() / base) { + return std::numeric_limits<uint64_t>::max(); + } + + result *= base; + + // Check for overflow on addition + if (result > std::numeric_limits<uint64_t>::max() - digit) { + return std::numeric_limits<uint64_t>::max(); + } + + result += digit; + } + + // If successful is not null and the entire string was parsed, + // set *successful to true (if not it is already false). + if (successful != nullptr && str == end) *successful = true; + + return result; + } + + 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); + } + + inline uint64_t string_to_uint64(const std::string_view str, bool* successful = nullptr, int base = 10) { + return string_to_uint64(str.data(), str.length(), successful, base); + } + + constexpr int64_t string_to_int64(char const* str, const char* const end, bool* successful = nullptr, int base = 10) { + if (successful != nullptr) *successful = false; + + if (str == nullptr || end <= str) return 0; + + // This flag will be set if the number is negative. + bool is_negative = false; + + // Check if there is a sign character. + if (*str == '+' || *str == '-') { + if (*str == '-') + is_negative = true; + ++str; + if (str == end) return 0; + } + + const uint64_t result = string_to_uint64(str, end, successful, base); + if (!is_negative) { + if (result >= std::numeric_limits<int64_t>::max()) + return std::numeric_limits<int64_t>::max(); + return result; + } else { + if (result > std::numeric_limits<int64_t>::max()) + return std::numeric_limits<int64_t>::min(); + return -result; + } + } + + 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(const std::string_view str, bool* successful = nullptr, int base = 10) { + return string_to_int64(str.data(), str.length(), successful, base); + } +} diff --git a/src/openvic/GameManager.cpp b/src/openvic/GameManager.cpp deleted file mode 100644 index 580dc4b..0000000 --- a/src/openvic/GameManager.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "GameManager.hpp" - -#include "utility/Logger.hpp" - -using namespace OpenVic; - -GameManager::GameManager(state_updated_func_t state_updated_callback) - : clock { [this]() { tick(); }, [this]() { update_state(); } }, - state_updated { state_updated_callback } {} - -void GameManager::set_needs_update() { - needs_update = true; -} - -void GameManager::update_state() { - if (needs_update) { - Logger::info("Update: ", today); - map.update_state(today); - if (state_updated) state_updated(); - needs_update = false; - } -} - -/* REQUIREMENTS: - * SS-98, SS-101 - */ -void GameManager::tick() { - today++; - Logger::info("Tick: ", today); - map.tick(today); - set_needs_update(); -} - -return_t GameManager::setup() { - session_start = time(nullptr); - clock.reset(); - today = { 1836 }; - good_manager.reset_to_defaults(); - return_t ret = map.setup(good_manager, building_manager, pop_manager); - set_needs_update(); - return ret; -} - -Date const& GameManager::get_today() const { - return today; -} - -return_t GameManager::expand_building(index_t province_index, const std::string_view building_type_identifier) { - set_needs_update(); - Province* province = map.get_province_by_index(province_index); - if (province == nullptr) return FAILURE; - return province->expand_building(building_type_identifier); -} diff --git a/src/openvic/Simulation.hpp b/src/openvic/Simulation.hpp deleted file mode 100644 index b0c2a19..0000000 --- a/src/openvic/Simulation.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -namespace OpenVic { - class Simulation {}; -}
\ No newline at end of file diff --git a/src/openvic/dataloader/Dataloader.hpp b/src/openvic/dataloader/Dataloader.hpp deleted file mode 100644 index 062f3d5..0000000 --- a/src/openvic/dataloader/Dataloader.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include <filesystem> -#include "../Simulation.hpp" - -namespace OpenVic { - class Dataloader { - public: - enum class LoadingMode { - DL_COMPATIBILITY - }; - - static bool loadDir(std::filesystem::path p, Simulation& sim, LoadingMode loadMode = LoadingMode::DL_COMPATIBILITY) { - return true; - } - }; -}
\ No newline at end of file diff --git a/src/openvic/economy/Good.cpp b/src/openvic/economy/Good.cpp deleted file mode 100644 index 9e8a7dd..0000000 --- a/src/openvic/economy/Good.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "Good.hpp" - -#include <cassert> - -using namespace OpenVic; - -Good::Good(const std::string_view new_identifier, colour_t new_colour, const std::string_view new_category, price_t new_base_price, - bool new_default_available, bool new_tradeable, bool new_currency, bool new_overseas_maintenance) - : HasIdentifierAndColour { new_identifier, new_colour, true }, - category { new_category }, - base_price { new_base_price }, - default_available { new_default_available }, - tradeable { new_tradeable }, - currency { new_currency }, - overseas_maintenance { new_overseas_maintenance } { - assert(base_price > NULL_PRICE); -} - -std::string const& Good::get_category() const { - return category; -} - -price_t Good::get_base_price() const { - return base_price; -} - -price_t Good::get_price() const { - return price; -} - -bool Good::is_default_available() const { - return default_available; -} - -bool Good::is_available() const { - return available; -} - -void Good::reset_to_defaults() { - available = default_available; - price = base_price; -} - -GoodManager::GoodManager() : goods { "goods" } {} - -return_t GoodManager::add_good(const std::string_view identifier, colour_t colour, const std::string_view category, - price_t base_price, bool default_available, bool tradeable, bool currency, bool overseas_maintenance) { - if (identifier.empty()) { - Logger::error("Invalid good identifier - empty!"); - return FAILURE; - } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid good colour for ", identifier, ": ", Good::colour_to_hex_string(colour)); - return FAILURE; - } - if (category.empty()) { - Logger::error("Invalid good category for ", identifier, ": empty!"); - return FAILURE; - } - if (base_price <= NULL_PRICE) { - Logger::error("Invalid base price for ", identifier, ": ", base_price); - return FAILURE; - } - return goods.add_item({ identifier, colour, category, base_price, default_available, tradeable, currency, overseas_maintenance }); -} - -void GoodManager::lock_goods() { - goods.lock(); -} - -void GoodManager::reset_to_defaults() { - for (Good& good : goods.get_items()) - good.reset_to_defaults(); -} - -Good const* GoodManager::get_good_by_index(size_t index) const { - return goods.get_item_by_index(index); -} - -Good const* GoodManager::get_good_by_identifier(const std::string_view identifier) const { - return goods.get_item_by_identifier(identifier); -} - -size_t GoodManager::get_good_count() const { - return goods.get_item_count(); -} - -std::vector<Good> const& GoodManager::get_goods() const { - return goods.get_items(); -} diff --git a/src/openvic/pop/Culture.cpp b/src/openvic/pop/Culture.cpp deleted file mode 100644 index f50bb23..0000000 --- a/src/openvic/pop/Culture.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "Culture.hpp" - -#include <cassert> - -using namespace OpenVic; - -GraphicalCultureType::GraphicalCultureType(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} - -CultureGroup::CultureGroup(const std::string_view new_identifier, - GraphicalCultureType const& new_unit_graphical_culture_type) - : HasIdentifier { new_identifier }, - unit_graphical_culture_type { new_unit_graphical_culture_type } {} - -GraphicalCultureType const& CultureGroup::get_unit_graphical_culture_type() const { - return unit_graphical_culture_type; -} - -Culture::Culture(const std::string_view new_identifier, colour_t new_colour, CultureGroup const& new_group, - name_list_t const& new_first_names, name_list_t const& new_last_names) - : HasIdentifierAndColour { new_identifier, new_colour, true }, - group { new_group }, - first_names { new_first_names }, - last_names { new_last_names } { -} - -CultureGroup const& Culture::get_group() const { - return group; -} - -CultureManager::CultureManager() - : graphical_culture_types { "graphical culture types" }, - culture_groups { "culture groups" }, - cultures { "cultures" } {} - -return_t CultureManager::add_graphical_culture_type(const std::string_view identifier) { - if (identifier.empty()) { - Logger::error("Invalid culture group identifier - empty!"); - return FAILURE; - } - return graphical_culture_types.add_item({ identifier }); -} - -void CultureManager::lock_graphical_culture_types() { - graphical_culture_types.lock(); -} - -GraphicalCultureType const* CultureManager::get_graphical_culture_type_by_identifier(const std::string_view identifier) const { - return graphical_culture_types.get_item_by_identifier(identifier); -} - -return_t CultureManager::add_culture_group(const std::string_view identifier, GraphicalCultureType const* graphical_culture_type) { - if (!graphical_culture_types.is_locked()) { - Logger::error("Cannot register culture groups until graphical culture types are locked!"); - return FAILURE; - } - if (identifier.empty()) { - Logger::error("Invalid culture group identifier - empty!"); - return FAILURE; - } - if (graphical_culture_type == nullptr) { - Logger::error("Null graphical culture type for ", identifier); - return FAILURE; - } - return culture_groups.add_item({ identifier, *graphical_culture_type }); -} - -void CultureManager::lock_culture_groups() { - culture_groups.lock(); -} - -CultureGroup const* CultureManager::get_culture_group_by_identifier(const std::string_view identifier) const { - return culture_groups.get_item_by_identifier(identifier); -} - -return_t CultureManager::add_culture(const std::string_view identifier, colour_t colour, CultureGroup const* group, Culture::name_list_t const& first_names, Culture::name_list_t const& last_names) { - if (!culture_groups.is_locked()) { - Logger::error("Cannot register cultures until culture groups are locked!"); - return FAILURE; - } - if (identifier.empty()) { - Logger::error("Invalid culture identifier - empty!"); - return FAILURE; - } - if (group == nullptr) { - Logger::error("Null culture group for ", identifier); - return FAILURE; - } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid culture colour for ", identifier, ": ", Culture::colour_to_hex_string(colour)); - return FAILURE; - } - // TODO - name list sanatisation? - return cultures.add_item({ identifier, colour, *group, first_names, last_names }); -} - -void CultureManager::lock_cultures() { - cultures.lock(); -} - -Culture const* CultureManager::get_culture_by_identifier(const std::string_view identifier) const { - return cultures.get_item_by_identifier(identifier); -} diff --git a/src/openvic/pop/Culture.hpp b/src/openvic/pop/Culture.hpp deleted file mode 100644 index 645e226..0000000 --- a/src/openvic/pop/Culture.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "../Types.hpp" - -namespace OpenVic { - - struct CultureManager; - - struct GraphicalCultureType : HasIdentifier { - friend struct CultureManager; - - private: - GraphicalCultureType(const std::string_view new_identifier); - - public: - GraphicalCultureType(GraphicalCultureType&&) = default; - }; - - struct CultureGroup : HasIdentifier { - friend struct CultureManager; - - private: - GraphicalCultureType const& unit_graphical_culture_type; - - // TODO - leader type, union tag - - CultureGroup(const std::string_view new_identifier, GraphicalCultureType const& new_unit_graphical_culture_type); - - public: - CultureGroup(CultureGroup&&) = default; - - GraphicalCultureType const& get_unit_graphical_culture_type() const; - }; - - struct Culture : HasIdentifierAndColour { - friend struct CultureManager; - - using name_list_t = std::vector<std::string>; - - private: - CultureGroup const& group; - const name_list_t first_names, last_names; - - // TODO - radicalism, primary tag - - Culture(const std::string_view new_identifier, colour_t new_colour, CultureGroup const& new_group, name_list_t const& new_first_names, name_list_t const& new_last_names); - - public: - Culture(Culture&&) = default; - - CultureGroup const& get_group() const; - }; - - struct CultureManager { - private: - IdentifierRegistry<GraphicalCultureType> graphical_culture_types; - IdentifierRegistry<CultureGroup> culture_groups; - IdentifierRegistry<Culture> cultures; - - public: - CultureManager(); - - return_t add_graphical_culture_type(const std::string_view identifier); - void lock_graphical_culture_types(); - GraphicalCultureType const* get_graphical_culture_type_by_identifier(const std::string_view identifier) const; - return_t add_culture_group(const std::string_view identifier, GraphicalCultureType const* new_graphical_culture_type); - void lock_culture_groups(); - CultureGroup const* get_culture_group_by_identifier(const std::string_view identifier) const; - return_t add_culture(const std::string_view identifier, colour_t colour, CultureGroup const* group, Culture::name_list_t const& first_names, Culture::name_list_t const& last_names); - void lock_cultures(); - Culture const* get_culture_by_identifier(const std::string_view identifier) const; - }; -} diff --git a/src/openvic/pop/Pop.cpp b/src/openvic/pop/Pop.cpp deleted file mode 100644 index 81915f6..0000000 --- a/src/openvic/pop/Pop.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "Pop.hpp" - -#include <cassert> - -#include "../map/Province.hpp" -#include "../utility/Logger.hpp" - -using namespace OpenVic; - -Pop::Pop(PopType const& new_type, Culture const& new_culture, Religion const& new_religion, pop_size_t new_size) - : type { new_type }, - culture { new_culture }, - religion { new_religion }, - size { new_size } { - assert(size > 0); -} - -PopType const& Pop::get_type() const { - return type; -} - -Culture const& Pop::get_culture() const { - return culture; -} - -Religion const& Pop::get_religion() const { - return religion; -} - -Pop::pop_size_t Pop::get_size() const { - return size; -} - -Pop::pop_size_t Pop::get_num_promoted() const { - return num_promoted; -} - -Pop::pop_size_t Pop::get_num_demoted() const { - return num_demoted; -} - -Pop::pop_size_t Pop::get_num_migrated() const { - return num_migrated; -} - -Pop::pop_size_t Pop::get_pop_daily_change() const { - return Pop::get_num_promoted() - (Pop::get_num_demoted() + Pop::get_num_migrated()); -} - -PopType::PopType(const std::string_view new_identifier, colour_t new_colour, - strata_t new_strata, sprite_t new_sprite, - Pop::pop_size_t new_max_size, Pop::pop_size_t new_merge_max_size, - bool new_state_capital_only, bool new_demote_migrant, bool new_is_artisan, bool new_is_slave) - : HasIdentifierAndColour { new_identifier, new_colour, true }, - strata { new_strata }, - sprite { new_sprite }, - max_size { new_max_size }, - merge_max_size { new_merge_max_size }, - state_capital_only { new_state_capital_only }, - demote_migrant { new_demote_migrant }, - is_artisan { new_is_artisan }, - is_slave { new_is_slave } { - assert(sprite > 0); - assert(max_size > 0); - assert(merge_max_size > 0); -} - -PopType::sprite_t PopType::get_sprite() const { - return sprite; -} - -PopType::strata_t PopType::get_strata() const { - return strata; -} - -Pop::pop_size_t PopType::get_max_size() const { - return max_size; -} - -Pop::pop_size_t PopType::get_merge_max_size() const { - return merge_max_size; -} - -bool PopType::get_state_capital_only() const { - return state_capital_only; -} - -bool PopType::get_demote_migrant() const { - return demote_migrant; -} - -bool PopType::get_is_artisan() const { - return is_artisan; -} - -bool PopType::get_is_slave() const { - return is_slave; -} - -static const std::string test_graphical_culture_type = "test_graphical_culture_type"; -static const std::string test_culture_group = "test_culture_group"; -static const std::string test_culture = "test_culture"; -static const std::string test_religion_group = "test_religion_group"; -static const std::string test_religion = "test_religion"; -static const std::string test_pop_type_poor = "test_pop_type_poor"; -static const std::string test_pop_type_middle = "test_pop_type_middle"; -static const std::string test_pop_type_rich = "test_pop_type_rich"; - - -PopManager::PopManager() : pop_types { "pop types" } { - culture_manager.add_graphical_culture_type(test_graphical_culture_type); - culture_manager.lock_graphical_culture_types(); - - culture_manager.add_culture_group(test_culture_group, culture_manager.get_graphical_culture_type_by_identifier(test_graphical_culture_type)); - culture_manager.lock_culture_groups(); - - culture_manager.add_culture(test_culture, 0x0000FF, culture_manager.get_culture_group_by_identifier(test_culture_group), - { "john" }, { "smith" }); - culture_manager.lock_cultures(); - - religion_manager.add_religion_group(test_religion_group); - religion_manager.lock_religion_groups(); - - religion_manager.add_religion(test_religion, 0xFF0000, religion_manager.get_religion_group_by_identifier(test_religion_group), 1, false); - religion_manager.lock_religions(); - - add_pop_type(test_pop_type_poor, 0xFF0000, PopType::strata_t::POOR, 1, 1, 1, false, false, false, false); - add_pop_type(test_pop_type_middle, 0x00FF00, PopType::strata_t::MIDDLE, 1, 1, 1, false, false, false, false); - add_pop_type(test_pop_type_rich, 0x0000FF, PopType::strata_t::RICH, 1, 1, 1, false, false, false, false); - lock_pop_types(); -} - -return_t PopManager::add_pop_type(const std::string_view identifier, colour_t colour, PopType::strata_t strata, PopType::sprite_t sprite, - Pop::pop_size_t max_size, Pop::pop_size_t merge_max_size, bool state_capital_only, bool demote_migrant, bool is_artisan, bool is_slave) { - if (identifier.empty()) { - Logger::error("Invalid pop type identifier - empty!"); - return FAILURE; - } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid pop type colour for ", identifier, ": ", PopType::colour_to_hex_string(colour)); - return FAILURE; - } - if (sprite <= 0) { - Logger::error("Invalid pop type sprite index for ", identifier, ": ", sprite); - return FAILURE; - } - if (max_size <= 0) { - Logger::error("Invalid pop type max size for ", identifier, ": ", max_size); - return FAILURE; - } - if (merge_max_size <= 0) { - Logger::error("Invalid pop type merge max size for ", identifier, ": ", merge_max_size); - return FAILURE; - } - return pop_types.add_item({ identifier, colour, strata, sprite, max_size, merge_max_size, state_capital_only, demote_migrant, is_artisan, is_slave }); -} - -void PopManager::lock_pop_types() { - pop_types.lock(); -} - -PopType const* PopManager::get_pop_type_by_identifier(const std::string_view identifier) const { - return pop_types.get_item_by_identifier(identifier); -} - -void PopManager::generate_test_pops(Province& province) const { - if (pop_types.is_locked()) { - static PopType const& type_poor = *get_pop_type_by_identifier(test_pop_type_poor); - static PopType const& type_middle = *get_pop_type_by_identifier(test_pop_type_middle); - static PopType const& type_rich = *get_pop_type_by_identifier(test_pop_type_rich); - static Culture const& culture = *culture_manager.get_culture_by_identifier(test_culture); - static Religion const& religion = *religion_manager.get_religion_by_identifier(test_religion); - - province.add_pop({ type_poor, culture, religion, static_cast<Pop::pop_size_t>(province.get_index() * province.get_index()) * 100 }); - province.add_pop({ type_middle, culture, religion, static_cast<Pop::pop_size_t>(province.get_index() * province.get_index()) * 50 }); - province.add_pop({ type_rich, culture, religion, static_cast<Pop::pop_size_t>(province.get_index()) * 1000 }); - } else { - Logger::error("Cannot generate pops before pop types registry is locked!"); - } -} diff --git a/src/openvic/pop/Religion.cpp b/src/openvic/pop/Religion.cpp deleted file mode 100644 index 0cfc7a6..0000000 --- a/src/openvic/pop/Religion.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "Religion.hpp" - -#include <cassert> - -using namespace OpenVic; - -ReligionGroup::ReligionGroup(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} - -Religion::Religion(const std::string_view new_identifier, colour_t new_colour, - ReligionGroup const& new_group, icon_t new_icon, bool new_pagan) - : HasIdentifierAndColour { new_identifier, new_colour, true }, - group { new_group }, - icon { new_icon }, - pagan { new_pagan } { - assert(icon > 0); -} - -ReligionGroup const& Religion::get_group() const { - return group; -} - -Religion::icon_t Religion::get_icon() const { - return icon; -} - -bool Religion::get_pagan() const { - return pagan; -} - -ReligionManager::ReligionManager() - : religion_groups { "religion groups" }, - religions { "religions" } {} - -return_t ReligionManager::add_religion_group(const std::string_view identifier) { - if (identifier.empty()) { - Logger::error("Invalid religion group identifier - empty!"); - return FAILURE; - } - return religion_groups.add_item({ identifier }); -} - -void ReligionManager::lock_religion_groups() { - religion_groups.lock(); -} - -ReligionGroup const* ReligionManager::get_religion_group_by_identifier(const std::string_view identifier) const { - return religion_groups.get_item_by_identifier(identifier); -} - -return_t ReligionManager::add_religion(const std::string_view identifier, colour_t colour, ReligionGroup const* group, Religion::icon_t icon, bool pagan) { - if (!religion_groups.is_locked()) { - Logger::error("Cannot register religions until religion groups are locked!"); - return FAILURE; - } - if (identifier.empty()) { - Logger::error("Invalid religion identifier - empty!"); - return FAILURE; - } - if (group == nullptr) { - Logger::error("Null religion group for ", identifier); - return FAILURE; - } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid religion colour for ", identifier, ": ", Religion::colour_to_hex_string(colour)); - return FAILURE; - } - if (icon <= 0) { - Logger::error("Invalid religion icon for ", identifier, ": ", icon); - return FAILURE; - } - return religions.add_item({ identifier, colour, *group, icon, pagan }); -} - -void ReligionManager::lock_religions() { - religions.lock(); -} - -Religion const* ReligionManager::get_religion_by_identifier(const std::string_view identifier) const { - return religions.get_item_by_identifier(identifier); -} |