Skip to content

Commit

Permalink
Implement artisanal production
Browse files Browse the repository at this point in the history
  • Loading branch information
wvpm committed Nov 19, 2024
1 parent aa58b23 commit 463e645
Show file tree
Hide file tree
Showing 20 changed files with 477 additions and 50 deletions.
8 changes: 7 additions & 1 deletion src/openvic-simulation/InstanceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,17 @@ bool InstanceManager::load_bookmark(Bookmark const* new_bookmark) {

today = bookmark->get_date();

const ArtisanalProducerFactoryPattern artisanal_producer_factory_pattern {
market_instance,
definition_manager.get_modifier_manager().get_modifier_effect_cache(),
definition_manager.get_economy_manager().get_production_type_manager()
};
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
);

ret &= country_instance_manager.apply_history_to_countries(
Expand Down
68 changes: 61 additions & 7 deletions src/openvic-simulation/economy/GoodInstance.cpp
Original file line number Diff line number Diff line change
@@ -1,37 +1,91 @@
#include "GoodInstance.hpp"
#include "types/fixed_point/FixedPoint.hpp"

using namespace OpenVic;

GoodInstance::GoodInstance(GoodDefinition const& new_good_definition)
: HasIdentifierAndColour { new_good_definition },
buy_lock { std::make_unique<std::mutex>() },
sell_lock { std::make_unique<std::mutex>() },
good_definition { new_good_definition },
price { new_good_definition.get_base_price() },
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() },
buy_up_to_orders {},
market_sell_orders {}
{}
{ update_next_price_limits(); }

void GoodInstance::update_next_price_limits() {
max_next_price = std::min(
good_definition.get_base_price() * 5,
price + fixed_point_t::_1() / fixed_point_t::_100()
);
min_next_price = std::max(
good_definition.get_base_price() * 22 / fixed_point_t::_100(),
price - fixed_point_t::_1() / fixed_point_t::_100()
);
}

void GoodInstance::add_buy_up_to_order(GoodBuyUpToOrder const&& 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 const&& 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;
fixed_point_t fraction_bought = fixed_point_t::_1();
fixed_point_t fraction_sold = fixed_point_t::_1();
if (demand_running_total > supply_running_total) {
new_price = max_next_price;
fraction_bought = supply_running_total / demand_running_total;
} else if (demand_running_total < supply_running_total) {
new_price = min_next_price;
fraction_sold = demand_running_total / supply_running_total;
} else {
new_price = price;
}

for (GoodBuyUpToOrder const& buy_up_to_order : buy_up_to_orders) {
const fixed_point_t quantity_bought = fraction_bought * buy_up_to_order.get_money_to_spend() / new_price;
buy_up_to_order.get_after_trade()({
quantity_bought,
buy_up_to_order.get_money_to_spend() - quantity_bought * new_price
});
}

for (GoodMarketSellOrder const& market_sell_order : market_sell_orders) {
const fixed_point_t quantity_sold = fraction_sold * market_sell_order.get_quantity();
market_sell_order.get_after_trade()({
market_sell_quantity,
market_sell_quantity * price
quantity_sold,
quantity_sold * new_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) {
Expand Down
8 changes: 8 additions & 0 deletions src/openvic-simulation/economy/GoodInstance.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#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/types/fixed_point/FixedPoint.hpp"
#include "openvic-simulation/types/HasIdentifier.hpp"
Expand All @@ -18,19 +19,26 @@ namespace OpenVic {
friend struct GoodInstanceManager;

private:
std::unique_ptr<std::mutex> buy_lock;
std::unique_ptr<std::mutex> sell_lock;
GoodDefinition const& PROPERTY(good_definition);
fixed_point_t PROPERTY(price);
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);
std::deque<GoodBuyUpToOrder> buy_up_to_orders;
std::deque<GoodMarketSellOrder> market_sell_orders;

GoodInstance(GoodDefinition const& new_good_definition);

void update_next_price_limits();
public:
GoodInstance(GoodInstance&&) = default;

//thread safe
void add_buy_up_to_order(GoodBuyUpToOrder const&& buy_up_to_order);
void add_market_sell_order(GoodMarketSellOrder const&& market_sell_order);

//not thread safe
Expand Down
121 changes: 114 additions & 7 deletions src/openvic-simulation/economy/production/ArtisanalProducer.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,120 @@
#include "ArtisanalProducer.hpp"

#include "openvic-simulation/economy/GoodDefinition.hpp"
#include "openvic-simulation/economy/trading/BuyResult.hpp"
#include "openvic-simulation/economy/trading/SellResult.hpp"
#include "openvic-simulation/pop/Pop.hpp"
#include "types/fixed_point/FixedPoint.hpp"

using namespace OpenVic;

ArtisanalProducer::ArtisanalProducer(
ProductionType const& new_production_type,
MarketInstance& new_market_instance,
ModifierEffectCache const& new_modifier_effect_cache,
Pop& new_pop,
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 },
stockpile { std::move(new_stockpile) },
current_production { new_current_production },
current_needs { std::move(new_current_needs) } {}
ProductionType const& new_production_type,
fixed_point_t new_current_production
) : market_instance { new_market_instance },
modifier_effect_cache { new_modifier_effect_cache },
pop { new_pop },
stockpile { new_stockpile },
production_type { new_production_type },
current_production { new_current_production }
{}

void ArtisanalProducer::artisan_tick() {
GoodDefinition::good_definition_map_t goods_to_buy_and_max_price { };
GoodDefinition::good_definition_map_t demand { };
fixed_point_t inputs_bought_scalar = fixed_point_t::_1();
if (production_type.get_input_goods().empty()) {
inputs_bought_scalar = fixed_point_t::_1();
} else {
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;
}
inputs_bought_scalar = std::min(stockpile[input_good_ptr] / desired_quantity, inputs_bought_scalar);
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_scalar > 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];
stockpile[input_good_ptr] = std::max(
fixed_point_t::_0(),
stockpile[input_good_ptr] - desired_quantity * inputs_bought_scalar
);

if (stockpile[input_good_ptr] >= 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 = 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];
}

max_possible_satisfaction = total_demand_value == fixed_point_t::_0()
? fixed_point_t::_1()
: std::min(
fixed_point_t::_1(),
(total_stockpile_value + total_cash_to_spend) / total_demand_value
);

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;
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 optimal_quantity = demand[input_good_ptr] * max_possible_satisfaction;
const fixed_point_t max_quantity_to_buy = demand[input_good_ptr] - stockpile[input_good_ptr];
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, input_good_ptr](const BuyResult buy_result) -> void {
pop.add_artisan_inputs_expense(-buy_result.get_money_left());
stockpile[input_good_ptr] += buy_result.get_quantity_bought();
}
});
}
}
}

current_production = production_type.get_base_output_quantity()
* inputs_bought_scalar
* 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,
[this](const SellResult sell_result) -> void {
pop.add_artisanal_income(sell_result.get_money_gained());
}
});
}
}
20 changes: 15 additions & 5 deletions src/openvic-simulation/economy/production/ArtisanalProducer.hpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
#pragma once

#include "openvic-simulation/economy/GoodDefinition.hpp"
#include "openvic-simulation/economy/production/ProductionType.hpp"
#include "openvic-simulation/economy/trading/MarketInstance.hpp"
#include "openvic-simulation/modifier/ModifierEffectCache.hpp"
#include "openvic-simulation/types/fixed_point/FixedPoint.hpp"
#include "openvic-simulation/utility/Getters.hpp"

namespace OpenVic {
struct Pop;

struct ArtisanalProducer {
private:
MarketInstance& market_instance;
ModifierEffectCache const& modifier_effect_cache;
Pop& pop;
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,
Pop& new_pop,
GoodDefinition::good_definition_map_t&& new_stockpile,
ProductionType const& new_production_type,
fixed_point_t new_current_production
);
void artisan_tick();
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "ArtisanalProducerFactoryPattern.hpp"

#include "openvic-simulation/economy/GoodInstance.hpp"

using namespace OpenVic;

ArtisanalProducerFactoryPattern::ArtisanalProducerFactoryPattern(
MarketInstance& new_market_instance,
ModifierEffectCache const& new_modifier_effect_cache,
ProductionTypeManager const& new_production_type_manager
) : market_instance { new_market_instance },
modifier_effect_cache { new_modifier_effect_cache },
production_type_manager { new_production_type_manager }
{}

std::unique_ptr<ArtisanalProducer> ArtisanalProducerFactoryPattern::CreateNewArtisanalProducer(Pop& pop) const {
ProductionType const* random_artisanal_production_type = nullptr;

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;
}
random_artisanal_production_type = &production_type;
break; //TODO actually randomise it instead of picking the first.
}
}

if (random_artisanal_production_type == nullptr) {
Logger::error("CreateNewArtisanalProducer was called but there are no artisanal production types.");
}

return std::make_unique<ArtisanalProducer>(
market_instance,
modifier_effect_cache,
pop,
GoodDefinition::good_definition_map_t{},
*random_artisanal_production_type,
fixed_point_t::_0()
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <memory>

#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:
MarketInstance& market_instance;
ModifierEffectCache const& modifier_effect_cache;
ProductionTypeManager const& production_type_manager;

public:
ArtisanalProducerFactoryPattern(
MarketInstance& new_market_instance,
ModifierEffectCache const& new_modifier_effect_cache,
ProductionTypeManager const& new_production_type_manager
);

std::unique_ptr<ArtisanalProducer> CreateNewArtisanalProducer(Pop& pop) const;
};
}
Loading

0 comments on commit 463e645

Please sign in to comment.