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 15, 2024
1 parent 1939e7c commit 4a8ef4d
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 17 deletions.
59 changes: 54 additions & 5 deletions src/openvic-simulation/economy/GoodInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,83 @@ 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(const GoodBuyUpToOrder buy_up_to_order) {
const std::lock_guard<std::mutex> lock {*buy_lock};
buy_up_to_orders.push_back(buy_up_to_order);
}

void GoodInstance::add_market_sell_order(const GoodMarketSellOrder market_sell_order) {
const std::lock_guard<std::mutex> lock {*sell_lock};
market_sell_orders.push_back(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) {
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;
} else if (demand_running_total < supply_running_total) {
new_price = min_next_price;
} else {
new_price = price;
}

for (GoodBuyUpToOrder const& buy_up_to_order : buy_up_to_orders) {
const fixed_point_t quantity_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 market_sell_quantity = market_sell_order.get_quantity();
supply_running_total += market_sell_quantity;
market_sell_order.get_after_trade()({
market_sell_quantity,
market_sell_quantity * price
market_sell_quantity * 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(const GoodBuyUpToOrder buy_up_to_order);
void add_market_sell_order(const GoodMarketSellOrder market_sell_order);

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

#include "openvic-simulation/economy/GoodDefinition.hpp"
#include "openvic-simulation/economy/trading/BuyResult.hpp"
#include "openvic-simulation/economy/trading/SellResult.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();
inputs_bought_scalar = std::min(stockpile[input_good_ptr] / desired_quantity, inputs_bought_scalar);
GoodInstance const& good = *good_instance_manager.get_good_instance_by_identifier(
input_good_ptr->get_identifier()
);
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 = 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 money_to_spend = optimal_quantity * max_price;
//TODO pop cash -= money_to_spend
market_instance.place_buy_up_to_order({
*input_good_ptr,
optimal_quantity,
money_to_spend,
[this, input_good_ptr](const BuyResult buy_result) -> void {
//TODO pop cash += 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 {
//TODO add artisanal income to pop (part of https://github.com/OpenVicProject/OpenVic-Simulation/issues/225 )
}
});
}
}
19 changes: 14 additions & 5 deletions src/openvic-simulation/economy/production/ArtisanalProducer.hpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
#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/pop/Pop.hpp"
#include "openvic-simulation/types/fixed_point/FixedPoint.hpp"
#include "openvic-simulation/utility/Getters.hpp"

namespace OpenVic {
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();
};
}
11 changes: 11 additions & 0 deletions src/openvic-simulation/economy/trading/BuyResult.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "BuyResult.hpp"

using namespace OpenVic;

BuyResult::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 }
{}
16 changes: 16 additions & 0 deletions src/openvic-simulation/economy/trading/BuyResult.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include "openvic-simulation/types/fixed_point/FixedPoint.hpp"

namespace OpenVic {
struct BuyResult {
private:
fixed_point_t PROPERTY(quantity_bought);
fixed_point_t PROPERTY(money_left);
public:
BuyResult(
const fixed_point_t new_quantity_bought,
const fixed_point_t new_money_left
);
};
}
25 changes: 25 additions & 0 deletions src/openvic-simulation/economy/trading/BuyUpToOrder.cpp
Original file line number Diff line number Diff line change
@@ -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,
const std::function<void(const BuyResult)> new_after_trade
) : max_quantity { new_max_quantity },
money_to_spend { new_money_to_spend },
after_trade { new_after_trade }
{}

BuyUpToOrder::BuyUpToOrder(
GoodDefinition const& new_good,
const fixed_point_t new_max_quantity,
const fixed_point_t new_money_to_spend,
const std::function<void(const BuyResult)> new_after_trade
) : GoodBuyUpToOrder(
new_max_quantity,
new_money_to_spend,
new_after_trade
),
good { &new_good }
{}
35 changes: 35 additions & 0 deletions src/openvic-simulation/economy/trading/BuyUpToOrder.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

#include "openvic-simulation/economy/GoodDefinition.hpp"
#include "openvic-simulation/economy/trading/BuyResult.hpp"
#include "openvic-simulation/types/fixed_point/FixedPoint.hpp"
#include "openvic-simulation/utility/Getters.hpp"

namespace OpenVic {
struct GoodBuyUpToOrder {
private:
const fixed_point_t PROPERTY(max_quantity);
const fixed_point_t PROPERTY(money_to_spend);
const std::function<void(const BuyResult)> PROPERTY(after_trade);

public:
GoodBuyUpToOrder(
const fixed_point_t new_max_quantity,
const fixed_point_t new_money_to_spend,
const std::function<void(const BuyResult)> new_after_trade
);
};

struct BuyUpToOrder : GoodBuyUpToOrder {
private:
GoodDefinition const* const PROPERTY(good);

public:
BuyUpToOrder(
GoodDefinition const& new_good,
const fixed_point_t new_max_quantity,
const fixed_point_t new_money_to_spend,
const std::function<void(const BuyResult)> new_after_trade
);
};
}
6 changes: 6 additions & 0 deletions src/openvic-simulation/economy/trading/MarketInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ bool MarketInstance::setup(GoodInstanceManager& new_good_instance_manager) {
return true;
}

void MarketInstance::place_buy_up_to_order(const BuyUpToOrder buy_up_to_order) {
GoodDefinition const* const good = buy_up_to_order.get_good();
GoodInstance* const good_instance = good_instance_manager->get_good_instance_by_identifier(good->get_identifier());
good_instance->add_buy_up_to_order(buy_up_to_order);
}

void MarketInstance::place_market_sell_order(const MarketSellOrder market_sell_order) {
GoodDefinition const* const good = market_sell_order.get_good();
GoodInstance* const good_instance = good_instance_manager->get_good_instance_by_identifier(good->get_identifier());
Expand Down
Loading

0 comments on commit 4a8ef4d

Please sign in to comment.