diff --git a/src/openvic-simulation/InstanceManager.cpp b/src/openvic-simulation/InstanceManager.cpp index 2177f6aa..e64884b1 100644 --- a/src/openvic-simulation/InstanceManager.cpp +++ b/src/openvic-simulation/InstanceManager.cpp @@ -11,10 +11,15 @@ InstanceManager::InstanceManager( gamestate_updated_func_t gamestate_updated_callback, SimulationClock::state_changed_function_t clock_state_changed_callback ) : game_rules_manager { new_game_rules_manager }, + market_instance { good_instance_manager }, + artisanal_producer_factory_pattern { + market_instance, + new_definition_manager.get_modifier_manager().get_modifier_effect_cache(), + new_definition_manager.get_economy_manager().get_production_type_manager() + }, definition_manager { new_definition_manager }, global_flags { "global" }, country_instance_manager { new_definition_manager.get_country_definition_manager() }, - market_instance { good_instance_manager }, politics_instance_manager { *this }, map_instance { new_definition_manager.get_map_definition() }, simulation_clock { @@ -78,7 +83,10 @@ bool InstanceManager::setup() { return false; } - bool ret = good_instance_manager.setup(definition_manager.get_economy_manager().get_good_definition_manager()); + bool ret = good_instance_manager.setup_goods( + definition_manager.get_economy_manager().get_good_definition_manager(), + game_rules_manager + ); ret &= map_instance.setup( definition_manager.get_economy_manager().get_building_type_manager(), market_instance, @@ -133,12 +141,12 @@ bool InstanceManager::load_bookmark(Bookmark const* new_bookmark) { today = bookmark->get_date(); politics_instance_manager.setup_starting_ideologies(); - bool ret = map_instance.apply_history_to_provinces( definition_manager.get_history_manager().get_province_manager(), today, country_instance_manager, // TODO - the following argument is for generating test pop attributes - definition_manager.get_politics_manager().get_issue_manager() + definition_manager.get_politics_manager().get_issue_manager(), + artisanal_producer_factory_pattern ); // It is important that province history is applied before country history as province history includes diff --git a/src/openvic-simulation/InstanceManager.hpp b/src/openvic-simulation/InstanceManager.hpp index c9480dfe..43715da4 100644 --- a/src/openvic-simulation/InstanceManager.hpp +++ b/src/openvic-simulation/InstanceManager.hpp @@ -23,14 +23,15 @@ namespace OpenVic { private: GameRulesManager const& game_rules_manager; + GoodInstanceManager PROPERTY_REF(good_instance_manager); + MarketInstance PROPERTY_REF(market_instance); + ArtisanalProducerFactoryPattern artisanal_producer_factory_pattern; DefinitionManager const& PROPERTY(definition_manager); FlagStrings PROPERTY_REF(global_flags); CountryInstanceManager PROPERTY_REF(country_instance_manager); CountryRelationManager PROPERTY_REF(country_relation_manager); - GoodInstanceManager PROPERTY_REF(good_instance_manager); - MarketInstance PROPERTY_REF(market_instance); UnitInstanceManager PROPERTY_REF(unit_instance_manager); PoliticsInstanceManager PROPERTY_REF(politics_instance_manager); /* Near the end so it is freed after other managers that may depend on it, diff --git a/src/openvic-simulation/economy/GoodInstance.cpp b/src/openvic-simulation/economy/GoodInstance.cpp index 620b047e..59ec9fd9 100644 --- a/src/openvic-simulation/economy/GoodInstance.cpp +++ b/src/openvic-simulation/economy/GoodInstance.cpp @@ -2,15 +2,57 @@ using namespace OpenVic; -GoodInstance::GoodInstance(GoodDefinition const& new_good_definition) +GoodInstance::GoodInstance(GoodDefinition const& new_good_definition, GameRulesManager const& new_game_rules_manager) : HasIdentifierAndColour { new_good_definition }, + buy_lock { std::make_unique() }, sell_lock { std::make_unique() }, + game_rules_manager { new_game_rules_manager }, good_definition { new_good_definition }, price { new_good_definition.get_base_price() }, + price_change_yesterday { fixed_point_t::_0() }, + max_next_price {}, + min_next_price {}, is_available { new_good_definition.get_is_available_from_start() }, + total_demand_yesterday { fixed_point_t::_0() }, total_supply_yesterday { fixed_point_t::_0() }, + quantity_traded_yesterday { fixed_point_t::_0() }, + buy_up_to_orders {}, market_sell_orders {} - {} + { update_next_price_limits(); } + +void GoodInstance::update_next_price_limits() { + if(game_rules_manager.get_use_exponential_price_changes()) { + const fixed_point_t max_change = price >> 6; + max_next_price = std::min( + fixed_point_t::usable_max(), + price + max_change + ); + min_next_price = std::max( + fixed_point_t::epsilon(), + price - max_change + ); + } else { + max_next_price = std::min( + std::min( + good_definition.get_base_price() * 5, + fixed_point_t::usable_max() + ), + price + fixed_point_t::_1() / fixed_point_t::_100() + ); + min_next_price = std::max( + std::max( + good_definition.get_base_price() * 22 / fixed_point_t::_100(), + fixed_point_t::epsilon() + ), + price - fixed_point_t::_1() / fixed_point_t::_100() + ); + } +} + +void GoodInstance::add_buy_up_to_order(GoodBuyUpToOrder&& buy_up_to_order) { + const std::lock_guard lock {*buy_lock}; + buy_up_to_orders.push_back(std::move(buy_up_to_order)); +} void GoodInstance::add_market_sell_order(GoodMarketSellOrder&& market_sell_order) { const std::lock_guard lock {*sell_lock}; @@ -18,23 +60,58 @@ void GoodInstance::add_market_sell_order(GoodMarketSellOrder&& market_sell_order } void GoodInstance::execute_orders() { - const fixed_point_t price = get_price(); + //MarketInstance ensured only orders with quantity > 0 are added. + //So running total > 0 unless orders are empty. + fixed_point_t demand_running_total = fixed_point_t::_0(); + for (GoodBuyUpToOrder const& buy_up_to_order : buy_up_to_orders) { + demand_running_total += buy_up_to_order.get_max_quantity(); + } fixed_point_t supply_running_total = fixed_point_t::_0(); - for(GoodMarketSellOrder const& market_sell_order : market_sell_orders) { - const fixed_point_t market_sell_quantity = market_sell_order.get_quantity(); - supply_running_total += market_sell_quantity; + for (GoodMarketSellOrder const& market_sell_order : market_sell_orders) { + supply_running_total += market_sell_order.get_quantity(); + } + + fixed_point_t new_price; + if (demand_running_total > supply_running_total) { + new_price = max_next_price; + quantity_traded_yesterday = supply_running_total; + } else if (demand_running_total < supply_running_total) { + new_price = min_next_price; + quantity_traded_yesterday = demand_running_total; + } else { + quantity_traded_yesterday = demand_running_total; + new_price = price; + } + + for (GoodBuyUpToOrder const& buy_up_to_order : buy_up_to_orders) { + const fixed_point_t money_spend = buy_up_to_order.get_money_to_spend() * quantity_traded_yesterday / demand_running_total; + const fixed_point_t quantity_bought = money_spend / new_price; + buy_up_to_order.get_after_trade()({ + quantity_bought, + buy_up_to_order.get_money_to_spend() - money_spend + }); + } + + for (GoodMarketSellOrder const& market_sell_order : market_sell_orders) { + const fixed_point_t quantity_sold = market_sell_order.get_quantity() * quantity_traded_yesterday / supply_running_total; market_sell_order.get_after_trade()({ - market_sell_quantity, - market_sell_quantity * price + quantity_sold, + quantity_sold * new_price }); } + price_change_yesterday = new_price - price; + total_demand_yesterday = demand_running_total; total_supply_yesterday = supply_running_total; + buy_up_to_orders.clear(); market_sell_orders.clear(); + if (new_price != price) { + update_next_price_limits(); + } } -bool GoodInstanceManager::setup(GoodDefinitionManager const& good_definition_manager) { +bool GoodInstanceManager::setup_goods(GoodDefinitionManager const& good_definition_manager, GameRulesManager const& game_rules_manager) { if (good_instances_are_locked()) { Logger::error("Cannot set up good instances - they are already locked!"); return false; @@ -45,7 +122,7 @@ bool GoodInstanceManager::setup(GoodDefinitionManager const& good_definition_man bool ret = true; for (GoodDefinition const& good : good_definition_manager.get_good_definitions()) { - ret &= good_instances.add_item({ good }); + ret &= good_instances.add_item({ good, game_rules_manager }); } lock_good_instances(); diff --git a/src/openvic-simulation/economy/GoodInstance.hpp b/src/openvic-simulation/economy/GoodInstance.hpp index 76c14a84..1e58cf0c 100644 --- a/src/openvic-simulation/economy/GoodInstance.hpp +++ b/src/openvic-simulation/economy/GoodInstance.hpp @@ -5,7 +5,9 @@ #include #include "openvic-simulation/economy/GoodDefinition.hpp" +#include "openvic-simulation/economy/trading/BuyUpToOrder.hpp" #include "openvic-simulation/economy/trading/MarketSellOrder.hpp" +#include "openvic-simulation/misc/GameRulesManager.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/types/HasIdentifier.hpp" #include "openvic-simulation/types/IdentifierRegistry.hpp" @@ -18,19 +20,29 @@ namespace OpenVic { friend struct GoodInstanceManager; private: + std::unique_ptr buy_lock; std::unique_ptr sell_lock; + GameRulesManager const& game_rules_manager; GoodDefinition const& PROPERTY(good_definition); fixed_point_t PROPERTY(price); + fixed_point_t PROPERTY(price_change_yesterday); + fixed_point_t PROPERTY(max_next_price); + fixed_point_t PROPERTY(min_next_price); bool PROPERTY(is_available); + fixed_point_t PROPERTY(total_demand_yesterday); fixed_point_t PROPERTY(total_supply_yesterday); + fixed_point_t PROPERTY(quantity_traded_yesterday); + std::deque buy_up_to_orders; std::deque market_sell_orders; - GoodInstance(GoodDefinition const& new_good_definition); + GoodInstance(GoodDefinition const& new_good_definition, GameRulesManager const& new_game_rules_manager); + void update_next_price_limits(); public: GoodInstance(GoodInstance&&) = default; //thread safe + void add_buy_up_to_order(GoodBuyUpToOrder&& buy_up_to_order); void add_market_sell_order(GoodMarketSellOrder&& market_sell_order); //not thread safe @@ -43,7 +55,7 @@ namespace OpenVic { public: IDENTIFIER_REGISTRY_NON_CONST_ACCESSORS(good_instance); - bool setup(GoodDefinitionManager const& good_definition_manager); + bool setup_goods(GoodDefinitionManager const& good_definition_manager, GameRulesManager const& game_rules_manager); GoodInstance& get_good_instance_from_definition(GoodDefinition const& good); GoodInstance const& get_good_instance_from_definition(GoodDefinition const& good) const; diff --git a/src/openvic-simulation/economy/production/ArtisanalProducer.cpp b/src/openvic-simulation/economy/production/ArtisanalProducer.cpp index d5cc3d31..91debd2a 100644 --- a/src/openvic-simulation/economy/production/ArtisanalProducer.cpp +++ b/src/openvic-simulation/economy/production/ArtisanalProducer.cpp @@ -1,13 +1,142 @@ #include "ArtisanalProducer.hpp" +#include "openvic-simulation/economy/GoodDefinition.hpp" +#include "openvic-simulation/economy/production/ProductionType.hpp" +#include "openvic-simulation/economy/trading/BuyResult.hpp" +#include "openvic-simulation/economy/trading/MarketInstance.hpp" +#include "openvic-simulation/economy/trading/SellResult.hpp" +#include "openvic-simulation/modifier/ModifierEffectCache.hpp" +#include "openvic-simulation/pop/Pop.hpp" + using namespace OpenVic; ArtisanalProducer::ArtisanalProducer( - ProductionType const& new_production_type, + MarketInstance& new_market_instance, + ModifierEffectCache const& new_modifier_effect_cache, GoodDefinition::good_definition_map_t&& new_stockpile, - fixed_point_t new_current_production, - GoodDefinition::good_definition_map_t&& new_current_needs -) : production_type { new_production_type }, + ProductionType const& new_production_type, + fixed_point_t new_current_production +) : market_instance { new_market_instance }, + modifier_effect_cache { new_modifier_effect_cache }, stockpile { std::move(new_stockpile) }, - current_production { new_current_production }, - current_needs { std::move(new_current_needs) } {} + production_type { new_production_type }, + current_production { new_current_production } + {} + +void ArtisanalProducer::artisan_tick(Pop& pop) { + GoodDefinition::good_definition_map_t goods_to_buy_and_max_price { }; + GoodDefinition::good_definition_map_t demand { }; + + //throughput scalar, the minimum of stockpile / desired_quantity + fixed_point_t inputs_bought_fraction = fixed_point_t::_1(), + inputs_bought_numerator= fixed_point_t::_1(), + inputs_bought_denominator= fixed_point_t::_1(); + if (!production_type.get_input_goods().empty()) { + GoodInstanceManager const& good_instance_manager = market_instance.get_good_instance_manager(); + for (auto const& [input_good_ptr, base_desired_quantity] : production_type.get_input_goods()) { + const fixed_point_t desired_quantity = demand[input_good_ptr] = base_desired_quantity * pop.get_size() / production_type.get_base_workforce_size(); + if (desired_quantity == fixed_point_t::_0()) { + continue; + } + const fixed_point_t good_bought_fraction = stockpile[input_good_ptr] / desired_quantity; + if(good_bought_fraction < inputs_bought_fraction) { + inputs_bought_fraction = good_bought_fraction; + inputs_bought_numerator = stockpile[input_good_ptr]; + inputs_bought_denominator = desired_quantity; + } + GoodInstance const& good = good_instance_manager.get_good_instance_from_definition(*input_good_ptr); + goods_to_buy_and_max_price[input_good_ptr] = good.get_max_next_price(); + } + + if (inputs_bought_fraction > fixed_point_t::_0()) { + for (auto const& [input_good_ptr, base_desired_quantity] : production_type.get_input_goods()) { + const fixed_point_t desired_quantity = demand[input_good_ptr]; + fixed_point_t& good_stockpile = stockpile[input_good_ptr]; + //Consume input good + good_stockpile = std::max( + fixed_point_t::_0(), + good_stockpile - desired_quantity * inputs_bought_numerator / inputs_bought_denominator + ); + + if (good_stockpile >= desired_quantity) { + goods_to_buy_and_max_price.erase(input_good_ptr); + } + } + } + + const fixed_point_t total_cash_to_spend = pop.get_cash(); + if (total_cash_to_spend > 0 && !goods_to_buy_and_max_price.empty()) { + //Figure out the optimal amount of goods to buy based on their price, stockpiled quantiy & demand + fixed_point_t max_possible_satisfaction_numerator= fixed_point_t::_1(), + max_possible_satisfaction_denominator= fixed_point_t::_1(); + + bool at_or_below_optimum = false; + while (!at_or_below_optimum) { + at_or_below_optimum = true; + fixed_point_t total_demand_value = fixed_point_t::_0(); + fixed_point_t total_stockpile_value = fixed_point_t::_0(); + for (auto const& [input_good_ptr, max_price] : goods_to_buy_and_max_price) { + total_demand_value += max_price * demand[input_good_ptr]; + total_stockpile_value += max_price * stockpile[input_good_ptr]; + } + + if ( total_demand_value == fixed_point_t::_0()) { + max_possible_satisfaction_numerator = fixed_point_t::_1(); + max_possible_satisfaction_denominator = fixed_point_t::_1(); + } else { + max_possible_satisfaction_numerator = total_stockpile_value + total_cash_to_spend; + max_possible_satisfaction_denominator = total_demand_value; + if(max_possible_satisfaction_numerator > max_possible_satisfaction_denominator) { + max_possible_satisfaction_numerator = fixed_point_t::_1(); + max_possible_satisfaction_denominator = fixed_point_t::_1(); + } + } + + for (auto const& [input_good_ptr, max_price] : goods_to_buy_and_max_price) { + const fixed_point_t optimal_quantity = demand[input_good_ptr] * max_possible_satisfaction_numerator / max_possible_satisfaction_denominator; + if (stockpile[input_good_ptr] >= optimal_quantity) { + goods_to_buy_and_max_price.erase(input_good_ptr); + at_or_below_optimum = false; + } + } + } + + //Place buy orders for each input + for (auto const& [input_good_ptr, max_price] : goods_to_buy_and_max_price) { + const fixed_point_t good_demand = demand[input_good_ptr]; + fixed_point_t& good_stockpile = stockpile[input_good_ptr]; + const fixed_point_t optimal_quantity = good_demand * max_possible_satisfaction_numerator / max_possible_satisfaction_denominator; + const fixed_point_t max_quantity_to_buy = good_demand - good_stockpile; + if (max_quantity_to_buy > 0) { + const fixed_point_t money_to_spend = optimal_quantity * max_price; + pop.add_artisan_inputs_expense(money_to_spend); + market_instance.place_buy_up_to_order({ + *input_good_ptr, + max_quantity_to_buy, + money_to_spend, + [this, &pop, &good_stockpile](const BuyResult buy_result) -> void { + pop.add_artisan_inputs_expense(-buy_result.get_money_left()); + good_stockpile += buy_result.get_quantity_bought(); + } + }); + } + } + } + } + + //Produce output & sell + current_production = production_type.get_base_output_quantity() + * inputs_bought_numerator * pop.get_size() + / (inputs_bought_denominator * production_type.get_base_workforce_size()); + + GoodDefinition const& output_good = production_type.get_output_good(); + if (current_production > 0) { + market_instance.place_market_sell_order({ + output_good, + current_production, + [&pop](const SellResult sell_result) -> void { + pop.add_artisanal_income(sell_result.get_money_gained()); + } + }); + } +} \ No newline at end of file diff --git a/src/openvic-simulation/economy/production/ArtisanalProducer.hpp b/src/openvic-simulation/economy/production/ArtisanalProducer.hpp index 65aa3fa0..2081bc63 100644 --- a/src/openvic-simulation/economy/production/ArtisanalProducer.hpp +++ b/src/openvic-simulation/economy/production/ArtisanalProducer.hpp @@ -1,22 +1,31 @@ #pragma once -#include "openvic-simulation/economy/GoodDefinition.hpp" -#include "openvic-simulation/economy/production/ProductionType.hpp" +#include "openvic-simulation/economy/GoodInstance.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/utility/Getters.hpp" namespace OpenVic { + struct MarketInstance; + struct ModifierEffectCache; + struct Pop; + struct ProductionType; + struct ArtisanalProducer { private: + MarketInstance& market_instance; + ModifierEffectCache const& modifier_effect_cache; + GoodDefinition::good_definition_map_t stockpile; ProductionType const& PROPERTY(production_type); - GoodDefinition::good_definition_map_t PROPERTY(stockpile); fixed_point_t PROPERTY(current_production); - GoodDefinition::good_definition_map_t PROPERTY(current_needs); public: ArtisanalProducer( - ProductionType const& new_production_type, GoodDefinition::good_definition_map_t&& new_stockpile, - fixed_point_t new_current_production, GoodDefinition::good_definition_map_t&& new_current_needs + MarketInstance& new_market_instance, + ModifierEffectCache const& new_modifier_effect_cache, + GoodDefinition::good_definition_map_t&& new_stockpile, + ProductionType const& new_production_type, + fixed_point_t new_current_production ); + void artisan_tick(Pop& pop); }; } diff --git a/src/openvic-simulation/economy/production/ArtisanalProducerFactoryPattern.cpp b/src/openvic-simulation/economy/production/ArtisanalProducerFactoryPattern.cpp new file mode 100644 index 00000000..4a27ffba --- /dev/null +++ b/src/openvic-simulation/economy/production/ArtisanalProducerFactoryPattern.cpp @@ -0,0 +1,54 @@ +#include "ArtisanalProducerFactoryPattern.hpp" + +using namespace OpenVic; + +ArtisanalProducerFactoryPattern::ArtisanalProducerFactoryPattern( + MarketInstance& new_market_instance, + ModifierEffectCache const& new_modifier_effect_cache, + ProductionTypeManager const& new_production_type_manager +) : index { -1 }, + unlocked_artisanal_production_types { }, + market_instance { new_market_instance }, + modifier_effect_cache { new_modifier_effect_cache }, + production_type_manager { new_production_type_manager } + { } + +std::unique_ptr ArtisanalProducerFactoryPattern::CreateNewArtisanalProducer() { + //TODO update unlocked_artisanal_production_types when goods are unlocked + if (index == -1) { + recalculate_unlocked_artisanal_production_types(); + } + + if (OV_unlikely(unlocked_artisanal_production_types.size() == 0)) { + Logger::error("CreateNewArtisanalProducer was called but there are no artisanal production types."); + return nullptr; + } + + //TODO select production type the way Victoria 2 does it (random?) + index = (index+1) % unlocked_artisanal_production_types.size(); + ProductionType const* random_artisanal_production_type = unlocked_artisanal_production_types[index]; + + return std::make_unique( + market_instance, + modifier_effect_cache, + GoodDefinition::good_definition_map_t{}, + *random_artisanal_production_type, + fixed_point_t::_0() + ); +} + +void ArtisanalProducerFactoryPattern::recalculate_unlocked_artisanal_production_types() { + unlocked_artisanal_production_types.clear(); + for (ProductionType const& production_type : production_type_manager.get_production_types()) { + if (production_type.get_template_type() == ProductionType::template_type_t::ARTISAN) { + GoodInstance const& good_instance = market_instance.get_good_instance_manager().get_good_instance_from_definition( + production_type.get_output_good() + ); + if (!good_instance.get_is_available()) { + continue; + } + + unlocked_artisanal_production_types.push_back(&production_type); + } + } +} \ No newline at end of file diff --git a/src/openvic-simulation/economy/production/ArtisanalProducerFactoryPattern.hpp b/src/openvic-simulation/economy/production/ArtisanalProducerFactoryPattern.hpp new file mode 100644 index 00000000..fc6f03fa --- /dev/null +++ b/src/openvic-simulation/economy/production/ArtisanalProducerFactoryPattern.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "openvic-simulation/economy/production/ArtisanalProducer.hpp" +#include "openvic-simulation/economy/production/ProductionType.hpp" +#include "openvic-simulation/economy/trading/MarketInstance.hpp" +#include "openvic-simulation/modifier/ModifierEffectCache.hpp" + +namespace OpenVic { + struct Pop; + + struct ArtisanalProducerFactoryPattern { + private: + int32_t index; + std::vector unlocked_artisanal_production_types; + MarketInstance& market_instance; + ModifierEffectCache const& modifier_effect_cache; + ProductionTypeManager const& production_type_manager; + + void recalculate_unlocked_artisanal_production_types(); + + public: + ArtisanalProducerFactoryPattern( + MarketInstance& new_market_instance, + ModifierEffectCache const& new_modifier_effect_cache, + ProductionTypeManager const& new_production_type_manager + ); + + std::unique_ptr CreateNewArtisanalProducer(); + }; +} \ No newline at end of file diff --git a/src/openvic-simulation/economy/trading/BuyResult.hpp b/src/openvic-simulation/economy/trading/BuyResult.hpp new file mode 100644 index 00000000..b3fb5e99 --- /dev/null +++ b/src/openvic-simulation/economy/trading/BuyResult.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" + +namespace OpenVic { + struct BuyResult { + private: + const fixed_point_t PROPERTY(quantity_bought); + const fixed_point_t PROPERTY(money_left); + public: + constexpr BuyResult( + const fixed_point_t new_quantity_bought, + const fixed_point_t new_money_left + ) : quantity_bought { new_quantity_bought }, + money_left { new_money_left } {} + }; +} \ No newline at end of file diff --git a/src/openvic-simulation/economy/trading/BuyUpToOrder.cpp b/src/openvic-simulation/economy/trading/BuyUpToOrder.cpp new file mode 100644 index 00000000..ddff357f --- /dev/null +++ b/src/openvic-simulation/economy/trading/BuyUpToOrder.cpp @@ -0,0 +1,25 @@ +#include "BuyUpToOrder.hpp" + +using namespace OpenVic; + +GoodBuyUpToOrder::GoodBuyUpToOrder( + const fixed_point_t new_max_quantity, + const fixed_point_t new_money_to_spend, + std::function&& new_after_trade +) : max_quantity { new_max_quantity }, + money_to_spend { new_money_to_spend }, + after_trade { std::move(new_after_trade) } + {} + +BuyUpToOrder::BuyUpToOrder( + GoodDefinition const& new_good, + const fixed_point_t new_max_quantity, + const fixed_point_t new_money_to_spend, + std::function&& new_after_trade +) : GoodBuyUpToOrder( + new_max_quantity, + new_money_to_spend, + std::move(new_after_trade) + ), + good { new_good } + {} \ No newline at end of file diff --git a/src/openvic-simulation/economy/trading/BuyUpToOrder.hpp b/src/openvic-simulation/economy/trading/BuyUpToOrder.hpp new file mode 100644 index 00000000..5b389fe2 --- /dev/null +++ b/src/openvic-simulation/economy/trading/BuyUpToOrder.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "openvic-simulation/economy/trading/BuyResult.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" +#include "openvic-simulation/utility/Getters.hpp" + +namespace OpenVic { + struct GoodDefinition; + + struct GoodBuyUpToOrder { + private: + const fixed_point_t PROPERTY(max_quantity); + const fixed_point_t PROPERTY(money_to_spend); + std::function PROPERTY(after_trade); + + public: + GoodBuyUpToOrder( + const fixed_point_t new_max_quantity, + const fixed_point_t new_money_to_spend, + std::function&& new_after_trade + ); + GoodBuyUpToOrder(GoodBuyUpToOrder&&) = default; + }; + + struct BuyUpToOrder : GoodBuyUpToOrder { + private: + GoodDefinition const& PROPERTY(good); + + public: + BuyUpToOrder( + GoodDefinition const& new_good, + const fixed_point_t new_max_quantity, + const fixed_point_t new_money_to_spend, + std::function&& new_after_trade + ); + BuyUpToOrder(BuyUpToOrder&&) = default; + }; +} \ No newline at end of file diff --git a/src/openvic-simulation/economy/trading/MarketInstance.cpp b/src/openvic-simulation/economy/trading/MarketInstance.cpp index 3c9198a2..bf532b38 100644 --- a/src/openvic-simulation/economy/trading/MarketInstance.cpp +++ b/src/openvic-simulation/economy/trading/MarketInstance.cpp @@ -1,19 +1,47 @@ #include "MarketInstance.hpp" +#include "openvic-simulation/utility/CompilerFeatureTesting.hpp" +#include "openvic-simulation/utility/Utility.hpp" + using namespace OpenVic; MarketInstance::MarketInstance(GoodInstanceManager& new_good_instance_manager) : good_instance_manager { new_good_instance_manager} {} +void MarketInstance::place_buy_up_to_order(BuyUpToOrder&& buy_up_to_order) { + GoodDefinition const& good = buy_up_to_order.get_good(); + if (OV_unlikely(buy_up_to_order.get_max_quantity() <= 0)) { + Logger::error("Received BuyUpToOrder for ",good," with max quantity ",buy_up_to_order.get_max_quantity()); + buy_up_to_order.get_after_trade()({ + fixed_point_t::_0(), buy_up_to_order.get_money_to_spend() + }); + return; + } + + GoodInstance& good_instance = good_instance_manager.get_good_instance_from_definition(good); + good_instance.add_buy_up_to_order(std::move(buy_up_to_order)); +} void MarketInstance::place_market_sell_order(MarketSellOrder&& market_sell_order) { GoodDefinition const& good = market_sell_order.get_good(); + if (OV_unlikely(market_sell_order.get_quantity() <= 0)) { + Logger::error("Received MarketSellOrder for ",good," with quantity ",market_sell_order.get_quantity()); + market_sell_order.get_after_trade()({ + fixed_point_t::_0(), fixed_point_t::_0() + }); + return; + } + GoodInstance& good_instance = good_instance_manager.get_good_instance_from_definition(good); good_instance.add_market_sell_order(std::move(market_sell_order)); } void MarketInstance::execute_orders() { - std::vector& good_instances = good_instance_manager.get_good_instances(); - for (GoodInstance& good_instance : good_instances) { - good_instance.execute_orders(); - } + auto& good_instances = good_instance_manager.get_good_instances(); + try_parallel_for_each( + good_instances.begin(), + good_instances.end(), + [](GoodInstance& good_instance) -> void { + good_instance.execute_orders(); + } + ); } diff --git a/src/openvic-simulation/economy/trading/MarketInstance.hpp b/src/openvic-simulation/economy/trading/MarketInstance.hpp index 3f7e3692..48bf1f8f 100644 --- a/src/openvic-simulation/economy/trading/MarketInstance.hpp +++ b/src/openvic-simulation/economy/trading/MarketInstance.hpp @@ -1,6 +1,7 @@ #pragma once #include "openvic-simulation/economy/GoodInstance.hpp" +#include "openvic-simulation/economy/trading/BuyUpToOrder.hpp" #include "openvic-simulation/economy/trading/MarketSellOrder.hpp" namespace OpenVic { @@ -9,6 +10,7 @@ namespace OpenVic { GoodInstanceManager& PROPERTY(good_instance_manager); public: MarketInstance(GoodInstanceManager& new_good_instance_manager); + void place_buy_up_to_order(BuyUpToOrder&& buy_up_to_order); void place_market_sell_order(MarketSellOrder&& market_sell_order); void execute_orders(); }; diff --git a/src/openvic-simulation/economy/trading/SellResult.hpp b/src/openvic-simulation/economy/trading/SellResult.hpp index 50f5de15..2490e638 100644 --- a/src/openvic-simulation/economy/trading/SellResult.hpp +++ b/src/openvic-simulation/economy/trading/SellResult.hpp @@ -5,8 +5,8 @@ namespace OpenVic { struct SellResult { private: - fixed_point_t PROPERTY(quantity_sold); - fixed_point_t PROPERTY(money_gained); + const fixed_point_t PROPERTY(quantity_sold); + const fixed_point_t PROPERTY(money_gained); public: constexpr SellResult( const fixed_point_t new_quantity_sold, diff --git a/src/openvic-simulation/map/MapInstance.cpp b/src/openvic-simulation/map/MapInstance.cpp index 2c4f9fe8..948cd109 100644 --- a/src/openvic-simulation/map/MapInstance.cpp +++ b/src/openvic-simulation/map/MapInstance.cpp @@ -2,6 +2,7 @@ #include "openvic-simulation/history/ProvinceHistory.hpp" #include "openvic-simulation/map/MapDefinition.hpp" +#include "openvic-simulation/utility/CompilerFeatureTesting.hpp" #include "openvic-simulation/utility/Logger.hpp" using namespace OpenVic; @@ -92,8 +93,11 @@ bool MapInstance::setup( } bool MapInstance::apply_history_to_provinces( - ProvinceHistoryManager const& history_manager, const Date date, CountryInstanceManager& country_manager, - IssueManager const& issue_manager + ProvinceHistoryManager const& history_manager, + const Date date, + CountryInstanceManager& country_manager, + IssueManager const& issue_manager, + ArtisanalProducerFactoryPattern& artisanal_producer_factory_pattern ) { bool ret = true; @@ -127,7 +131,7 @@ bool MapInstance::apply_history_to_provinces( if (pop_history_entry == nullptr) { Logger::warning("No pop history entry for province ", province.get_identifier(), " for date ", date); } else { - ret &= province.add_pop_vec(pop_history_entry->get_pops()); + ret &= province.add_pop_vec(pop_history_entry->get_pops(), artisanal_producer_factory_pattern); province.setup_pop_test_values(issue_manager); } @@ -164,9 +168,14 @@ void MapInstance::update_gamestate(const Date today, DefineManager const& define } void MapInstance::map_tick(const Date today) { - for (ProvinceInstance& province : province_instances.get_items()) { - province.province_tick(today); - } + auto& provinces = province_instances.get_items(); + try_parallel_for_each( + provinces.begin(), + provinces.end(), + [today](ProvinceInstance& province) -> void { + province.province_tick(today); + } + ); } void MapInstance::initialise_for_new_game( @@ -174,8 +183,13 @@ void MapInstance::initialise_for_new_game( DefineManager const& define_manager ) { update_gamestate(today, define_manager); - for (ProvinceInstance& province : province_instances.get_items()) { - province.initialise_rgo(); - province.province_tick(today); - } + auto& provinces = province_instances.get_items(); + try_parallel_for_each( + provinces.begin(), + provinces.end(), + [today](ProvinceInstance& province) -> void { + province.initialise_rgo(); + province.province_tick(today); + } + ); } \ No newline at end of file diff --git a/src/openvic-simulation/map/MapInstance.hpp b/src/openvic-simulation/map/MapInstance.hpp index efe8ddc1..3841a7e6 100644 --- a/src/openvic-simulation/map/MapInstance.hpp +++ b/src/openvic-simulation/map/MapInstance.hpp @@ -53,8 +53,11 @@ namespace OpenVic { decltype(ProvinceInstance::ideology_distribution)::keys_type const& ideology_keys ); bool apply_history_to_provinces( - ProvinceHistoryManager const& history_manager, const Date date, CountryInstanceManager& country_manager, - IssueManager const& issue_manager + ProvinceHistoryManager const& history_manager, + const Date date, + CountryInstanceManager& country_manager, + IssueManager const& issue_manager, + ArtisanalProducerFactoryPattern& artisanal_producer_factory_pattern ); void update_modifier_sums(const Date today, StaticModifierCache const& static_modifier_cache); diff --git a/src/openvic-simulation/map/ProvinceInstance.cpp b/src/openvic-simulation/map/ProvinceInstance.cpp index 73661493..fa46d33f 100644 --- a/src/openvic-simulation/map/ProvinceInstance.cpp +++ b/src/openvic-simulation/map/ProvinceInstance.cpp @@ -12,6 +12,7 @@ #include "openvic-simulation/military/UnitInstanceGroup.hpp" #include "openvic-simulation/modifier/StaticModifierCache.hpp" #include "openvic-simulation/politics/Ideology.hpp" +#include "openvic-simulation/utility/CompilerFeatureTesting.hpp" #include "openvic-simulation/utility/Logger.hpp" using namespace OpenVic; @@ -206,11 +207,15 @@ bool ProvinceInstance::add_pop(Pop&& pop) { } } -bool ProvinceInstance::add_pop_vec(std::vector const& pop_vec) { +bool ProvinceInstance::add_pop_vec(std::vector const& pop_vec, ArtisanalProducerFactoryPattern& artisanal_producer_factory_pattern) { if (!province_definition.is_water()) { reserve_more(pops, pop_vec.size()); for (PopBase const& pop : pop_vec) { - _add_pop(Pop { pop, *ideology_distribution.get_keys() }); + _add_pop(Pop { + pop, + *ideology_distribution.get_keys(), + artisanal_producer_factory_pattern + }); } return true; } else { @@ -426,6 +431,13 @@ void ProvinceInstance::update_gamestate(const Date today, DefineManager const& d } void ProvinceInstance::province_tick(const Date today) { + try_parallel_for_each( + pops.begin(), + pops.end(), + [](Pop& pop) -> void { + pop.pop_tick(); + } + ); for (BuildingInstance& building : buildings.get_items()) { building.tick(today); } diff --git a/src/openvic-simulation/map/ProvinceInstance.hpp b/src/openvic-simulation/map/ProvinceInstance.hpp index 6f3f8964..5b243fdf 100644 --- a/src/openvic-simulation/map/ProvinceInstance.hpp +++ b/src/openvic-simulation/map/ProvinceInstance.hpp @@ -203,7 +203,7 @@ namespace OpenVic { bool expand_building(size_t building_index); bool add_pop(Pop&& pop); - bool add_pop_vec(std::vector const& pop_vec); + bool add_pop_vec(std::vector const& pop_vec, ArtisanalProducerFactoryPattern& artisanal_producer_factory_pattern); size_t get_pop_count() const; void update_modifier_sum(Date today, StaticModifierCache const& static_modifier_cache); diff --git a/src/openvic-simulation/misc/GameRulesManager.hpp b/src/openvic-simulation/misc/GameRulesManager.hpp index 1d3d3357..5b8ac54b 100644 --- a/src/openvic-simulation/misc/GameRulesManager.hpp +++ b/src/openvic-simulation/misc/GameRulesManager.hpp @@ -5,6 +5,6 @@ namespace OpenVic { struct GameRulesManager { private: bool PROPERTY_RW(use_simple_farm_mine_logic, false); - bool PROPERTY_RW(display_rgo_eff_tech_throughput_effect, false); + bool PROPERTY_RW(use_exponential_price_changes, false); }; } \ No newline at end of file diff --git a/src/openvic-simulation/pop/Pop.cpp b/src/openvic-simulation/pop/Pop.cpp index b18d5faa..cdfa48cb 100644 --- a/src/openvic-simulation/pop/Pop.cpp +++ b/src/openvic-simulation/pop/Pop.cpp @@ -1,8 +1,13 @@ +#include "utility/Utility.hpp" #define KEEP_DO_FOR_ALL_TYPES_OF_INCOME +#define KEEP_DO_FOR_ALL_TYPES_OF_EXPENSES #include "Pop.hpp" #undef KEEP_DO_FOR_ALL_TYPES_OF_INCOME +#undef KEEP_DO_FOR_ALL_TYPES_OF_EXPENSES #include "openvic-simulation/DefinitionManager.hpp" +#include "openvic-simulation/economy/production/ArtisanalProducer.hpp" +#include "openvic-simulation/economy/production/ArtisanalProducerFactoryPattern.hpp" using namespace OpenVic; @@ -14,8 +19,17 @@ PopBase::PopBase( ) : type { &new_type }, culture { new_culture }, religion { new_religion }, size { new_size }, militancy { new_militancy }, consciousness { new_consciousness }, rebel_type { new_rebel_type } {} -Pop::Pop(PopBase const& pop_base, decltype(ideology_distribution)::keys_type const& ideology_keys) +Pop::Pop( + PopBase const& pop_base, + decltype(ideology_distribution)::keys_type const& ideology_keys, + ArtisanalProducerFactoryPattern& artisanal_producer_factory_pattern +) : PopBase { pop_base }, + artisanal_producer_nullable { + type->get_is_artisan() + ? artisanal_producer_factory_pattern.CreateNewArtisanalProducer() + : nullptr + }, location { nullptr }, total_change { 0 }, num_grown { 0 }, @@ -36,11 +50,12 @@ Pop::Pop(PopBase const& pop_base, decltype(ideology_distribution)::keys_type con life_needs_fulfilled { 0 }, everyday_needs_fulfilled { 0 }, luxury_needs_fulfilled { 0 }, - #define INITALIZE_POP_INCOME_STORES(name)\ + #define INITALIZE_POP_MONEY_STORES(name)\ name { 0 }, - DO_FOR_ALL_TYPES_OF_POP_INCOME(INITALIZE_POP_INCOME_STORES) - #undef INITALIZE_POP_INCOME_STORES + DO_FOR_ALL_TYPES_OF_POP_INCOME(INITALIZE_POP_MONEY_STORES) + DO_FOR_ALL_TYPES_OF_POP_EXPENSES(INITALIZE_POP_MONEY_STORES) + #undef INITALIZE_POP_MONEY_STORES max_supported_regiments { 0 } {} void Pop::setup_pop_test_values(IssueManager const& issue_manager) { @@ -200,20 +215,68 @@ void Pop::update_gamestate( } } +std::stringstream Pop::get_pop_context_text() const { + std::stringstream pop_context {}; + pop_context << " location: "; + if (OV_unlikely(location == nullptr)) { + pop_context << "NULL"; + } else { + pop_context << location->get_identifier(); + } + pop_context << " type: " << type << " culture: " << culture << " religion: " << religion << " size: " << size; + return pop_context; +} + #define DEFINE_ADD_INCOME_FUNCTIONS(name)\ - void Pop::add_##name(const fixed_point_t pop_income){\ - name += pop_income;\ - income += pop_income;\ + void Pop::add_##name(const fixed_point_t amount){\ + if (OV_unlikely(amount == 0)) { \ + Logger::warning("Adding ",#name, " of 0 to pop. Context", get_pop_context_text().str()); \ + return; \ + } \ + if (OV_unlikely(amount < 0)) { \ + Logger::error("Adding negative ", #name, " of ",amount," to pop. Context", get_pop_context_text().str()); \ + return; \ + } \ + name += amount;\ + income += amount;\ + cash += amount;\ } DO_FOR_ALL_TYPES_OF_POP_INCOME(DEFINE_ADD_INCOME_FUNCTIONS) #undef DEFINE_ADD_INCOME_FUNCTIONS +#define DEFINE_ADD_EXPENSE_FUNCTIONS(name)\ + void Pop::add_##name(const fixed_point_t amount){\ + if (OV_unlikely(amount == 0)) { \ + Logger::warning("Adding ",#name, " of 0 to pop. Context", get_pop_context_text().str()); \ + return; \ + } \ + name += amount;\ + expenses += amount;\ + if (OV_unlikely(expenses < 0)) { \ + Logger::error("Total expenses became negative (",expenses,") after adding ", #name, " of ",amount," to pop. Context", get_pop_context_text().str()); \ + } \ + cash -= amount;\ + if (OV_unlikely(cash < 0)) { \ + Logger::error("Total cash became negative (",cash,") after adding ", #name, " of ",amount," to pop. Context", get_pop_context_text().str()); \ + } \ + } + +DO_FOR_ALL_TYPES_OF_POP_EXPENSES(DEFINE_ADD_EXPENSE_FUNCTIONS) +#undef DEFINE_ADD_EXPENSE_FUNCTIONS + #define SET_ALL_INCOME_TO_ZERO(name)\ name = fixed_point_t::_0(); -void Pop::clear_all_income(){ +void Pop::pop_tick() { DO_FOR_ALL_TYPES_OF_POP_INCOME(SET_ALL_INCOME_TO_ZERO) #undef DO_FOR_ALL_TYPES_OF_POP_INCOME #undef SET_ALL_INCOME_TO_ZERO -} \ No newline at end of file + + if (artisanal_producer_nullable != nullptr) { + artisanal_producer_nullable->artisan_tick(*this); + } +} + +#undef DO_FOR_ALL_TYPES_OF_POP_INCOME +#undef DO_FOR_ALL_TYPES_OF_POP_EXPENSES \ No newline at end of file diff --git a/src/openvic-simulation/pop/Pop.hpp b/src/openvic-simulation/pop/Pop.hpp index a02f7f20..3c5db5b4 100644 --- a/src/openvic-simulation/pop/Pop.hpp +++ b/src/openvic-simulation/pop/Pop.hpp @@ -1,6 +1,9 @@ #pragma once +#include + #include "openvic-simulation/country/CountryDefinition.hpp" +#include "openvic-simulation/economy/production/ArtisanalProducerFactoryPattern.hpp" #include "openvic-simulation/pop/PopType.hpp" namespace OpenVic { @@ -39,12 +42,18 @@ namespace OpenVic { F(government_salary_military)\ F(event_and_decision_income)\ F(loan_interest_payments) - - #define DECLARE_POP_INCOME_STORES(income_type)\ - fixed_point_t PROPERTY(income_type); - - #define DECLARE_POP_INCOME_STORE_FUNCTIONS(name)\ - void add_##name(const fixed_point_t pop_income); + + #define DO_FOR_ALL_TYPES_OF_POP_EXPENSES(F)\ + F(life_needs_expense)\ + F(everyday_needs_expense)\ + F(luxury_needs_expense)\ + F(artisan_inputs_expense) + + #define DECLARE_POP_MONEY_STORES(money_type)\ + fixed_point_t PROPERTY(money_type); + + #define DECLARE_POP_MONEY_STORE_FUNCTIONS(name)\ + void add_##name(const fixed_point_t amount); /* REQUIREMENTS: * POP-18, POP-19, POP-20, POP-21, POP-34, POP-35, POP-36, POP-37 @@ -55,6 +64,7 @@ namespace OpenVic { static constexpr pop_size_t MAX_SIZE = std::numeric_limits::max(); private: + std::unique_ptr artisanal_producer_nullable; ProvinceInstance const* PROPERTY(location); /* Last day's size change by source. */ @@ -78,18 +88,25 @@ namespace OpenVic { fixed_point_t PROPERTY(unemployment); fixed_point_t PROPERTY(cash); fixed_point_t PROPERTY(income); - fixed_point_t PROPERTY(expenses); + fixed_point_t PROPERTY(expenses); //positive value means POP paid for goods. This is displayed * -1 in UI. fixed_point_t PROPERTY(savings); fixed_point_t PROPERTY(life_needs_fulfilled); fixed_point_t PROPERTY(everyday_needs_fulfilled); fixed_point_t PROPERTY(luxury_needs_fulfilled); - DO_FOR_ALL_TYPES_OF_POP_INCOME(DECLARE_POP_INCOME_STORES); - #undef DECLARE_POP_INCOME_STORES - + DO_FOR_ALL_TYPES_OF_POP_INCOME(DECLARE_POP_MONEY_STORES); + DO_FOR_ALL_TYPES_OF_POP_EXPENSES(DECLARE_POP_MONEY_STORES); + #undef DECLARE_POP_MONEY_STORES + size_t PROPERTY(max_supported_regiments); - Pop(PopBase const& pop_base, decltype(ideology_distribution)::keys_type const& ideology_keys); + Pop( + PopBase const& pop_base, + decltype(ideology_distribution)::keys_type const& ideology_keys, + ArtisanalProducerFactoryPattern& artisanal_producer_factory_pattern + ); + + std::stringstream get_pop_context_text() const; public: Pop(Pop const&) = delete; @@ -114,12 +131,16 @@ namespace OpenVic { const fixed_point_t pop_size_per_regiment_multiplier ); - DO_FOR_ALL_TYPES_OF_POP_INCOME(DECLARE_POP_INCOME_STORE_FUNCTIONS) - #undef DECLARE_POP_INCOME_STORE_FUNCTIONS - void clear_all_income(); - + DO_FOR_ALL_TYPES_OF_POP_INCOME(DECLARE_POP_MONEY_STORE_FUNCTIONS) + DO_FOR_ALL_TYPES_OF_POP_EXPENSES(DECLARE_POP_MONEY_STORE_FUNCTIONS) + #undef DECLARE_POP_MONEY_STORE_FUNCTIONS + void pop_tick(); }; } #ifndef KEEP_DO_FOR_ALL_TYPES_OF_INCOME #undef DO_FOR_ALL_TYPES_OF_POP_INCOME #endif + +#ifndef KEEP_DO_FOR_ALL_TYPES_OF_EXPENSES + #undef DO_FOR_ALL_TYPES_OF_POP_EXPENSES +#endif diff --git a/src/openvic-simulation/types/fixed_point/FixedPoint.hpp b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp index 08d14f23..9b47f97d 100644 --- a/src/openvic-simulation/types/fixed_point/FixedPoint.hpp +++ b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp @@ -67,6 +67,10 @@ namespace OpenVic { return -usable_max(); } + static constexpr fixed_point_t epsilon() { + return parse_raw(1); + } + static constexpr fixed_point_t _0() { return 0; } @@ -457,6 +461,14 @@ namespace OpenVic { return old; } + 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>>(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value >> rhs; + } + constexpr friend fixed_point_t operator*(fixed_point_t const& lhs, fixed_point_t const& rhs) { return lhs.value * rhs.value >> PRECISION; } diff --git a/src/openvic-simulation/utility/CompilerFeatureTesting.hpp b/src/openvic-simulation/utility/CompilerFeatureTesting.hpp new file mode 100644 index 00000000..b77a4639 --- /dev/null +++ b/src/openvic-simulation/utility/CompilerFeatureTesting.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace OpenVic { + template + inline constexpr void try_parallel_for_each( + InputIt first, + InputIt last, + UnaryFunc f + ) { + #ifndef __cpp_lib_execution + std::for_each(first, last, f); + #else + #ifdef __unix__ + std::for_each(first, last, f); + #else + if constexpr (__cpp_lib_execution >= 201603L) { + std::for_each(std::execution::par, first, last, f); + } else { + std::for_each(first, last, f); + } + #endif + #endif + } +} \ No newline at end of file