Skip to content

Commit

Permalink
Implement artisanal production
Browse files Browse the repository at this point in the history
  • Loading branch information
wvpm committed Jan 26, 2025
1 parent 1e310b3 commit 72dac44
Show file tree
Hide file tree
Showing 23 changed files with 609 additions and 74 deletions.
16 changes: 12 additions & 4 deletions src/openvic-simulation/InstanceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/openvic-simulation/InstanceManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ namespace OpenVic {

private:
GameRulesManager const& game_rules_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,
Expand Down
96 changes: 86 additions & 10 deletions src/openvic-simulation/economy/GoodInstance.cpp
Original file line number Diff line number Diff line change
@@ -1,40 +1,116 @@
#include "GoodInstance.hpp"
#include "types/fixed_point/FixedPoint.hpp"

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<std::mutex>() },
sell_lock { std::make_unique<std::mutex>() },
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<std::mutex> 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<std::mutex> lock {*sell_lock};
market_sell_orders.push_back(std::move(market_sell_order));
}

void GoodInstance::execute_orders() {
const fixed_point_t price = get_price();
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;
Expand All @@ -45,7 +121,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();
Expand Down
16 changes: 14 additions & 2 deletions src/openvic-simulation/economy/GoodInstance.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#include <mutex>

#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"
Expand All @@ -18,19 +20,29 @@ namespace OpenVic {
friend struct GoodInstanceManager;

private:
std::unique_ptr<std::mutex> buy_lock;
std::unique_ptr<std::mutex> 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<GoodBuyUpToOrder> buy_up_to_orders;
std::deque<GoodMarketSellOrder> 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
Expand All @@ -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;
Expand Down
133 changes: 127 additions & 6 deletions src/openvic-simulation/economy/production/ArtisanalProducer.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,134 @@
#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 { };
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];
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()) {
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;
}
}
}

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;
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();
}
});
}
}
}

current_production = production_type.get_base_output_quantity()
* inputs_bought_numerator / inputs_bought_denominator
* pop.get_size() / 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());
}
});
}
}
Loading

0 comments on commit 72dac44

Please sign in to comment.