From 2121a03874c77ad277bdfdb689499b6d3b4f9832 Mon Sep 17 00:00:00 2001 From: wvpm <24685035+wvpm@users.noreply.github.com> Date: Wed, 20 May 2026 21:41:24 +0200 Subject: [PATCH] Let main thread also work --- .../population/PopValuesFromProvince.cpp | 17 - .../population/PopValuesFromProvince.hpp | 12 +- src/openvic-simulation/utility/ThreadPool.cpp | 335 ++++++++++-------- src/openvic-simulation/utility/ThreadPool.hpp | 3 - 4 files changed, 207 insertions(+), 160 deletions(-) diff --git a/src/openvic-simulation/population/PopValuesFromProvince.cpp b/src/openvic-simulation/population/PopValuesFromProvince.cpp index d40f71ba6..5035edbf0 100644 --- a/src/openvic-simulation/population/PopValuesFromProvince.cpp +++ b/src/openvic-simulation/population/PopValuesFromProvince.cpp @@ -40,23 +40,6 @@ void PopStrataValuesFromProvince::update_pop_strata_values_from_province( * (fixed_point_t::_1 + province.get_modifier_effect_value(*strata_effects.get_luxury_needs())); } -PopValuesFromProvince::PopValuesFromProvince( - GameRulesManager const& new_game_rules_manager, - GoodInstanceManager const& new_good_instance_manager, - ModifierEffectCache const& new_modifier_effect_cache, - ProductionTypeManager const& new_production_type_manager, - PopsDefines const& new_defines, - const strata_index_t strata_size -) : game_rules_manager { new_game_rules_manager }, - good_instance_manager { new_good_instance_manager }, - modifier_effect_cache { new_modifier_effect_cache }, - production_type_manager { new_production_type_manager }, - defines { new_defines }, - effects_by_strata { - generate_values, - strata_size - } {} - void PopValuesFromProvince::update_pop_values_from_province(ProvinceInstance& province) { for (auto& values : effects_by_strata) { values.update_pop_strata_values_from_province(defines, modifier_effect_cache, province); diff --git a/src/openvic-simulation/population/PopValuesFromProvince.hpp b/src/openvic-simulation/population/PopValuesFromProvince.hpp index 84150bdc1..5fda3f173 100644 --- a/src/openvic-simulation/population/PopValuesFromProvince.hpp +++ b/src/openvic-simulation/population/PopValuesFromProvince.hpp @@ -47,14 +47,22 @@ namespace OpenVic { PopsDefines const& defines; GameRulesManager const& game_rules_manager; - PopValuesFromProvince( + constexpr PopValuesFromProvince( GameRulesManager const& new_game_rules_manager, GoodInstanceManager const& new_good_instance_manager, ModifierEffectCache const& new_modifier_effect_cache, ProductionTypeManager const& new_production_type_manager, PopsDefines const& new_defines, const strata_index_t strata_size - ); + ) : game_rules_manager { new_game_rules_manager }, + good_instance_manager { new_good_instance_manager }, + modifier_effect_cache { new_modifier_effect_cache }, + production_type_manager { new_production_type_manager }, + defines { new_defines }, + effects_by_strata { + generate_values, + strata_size + } {} void update_pop_values_from_province(ProvinceInstance& province); }; diff --git a/src/openvic-simulation/utility/ThreadPool.cpp b/src/openvic-simulation/utility/ThreadPool.cpp index 1f91c627d..0671a02e2 100644 --- a/src/openvic-simulation/utility/ThreadPool.cpp +++ b/src/openvic-simulation/utility/ThreadPool.cpp @@ -1,6 +1,11 @@ #include "ThreadPool.hpp" #include "ThreadDeps.hpp" +#include +#include +#include +#include + #include "openvic-simulation/country/CountryInstance.hpp" #include "openvic-simulation/economy/GoodDefinition.hpp" // IWYU pragma: keep for constructor requirement #include "openvic-simulation/economy/GoodInstance.hpp" @@ -9,38 +14,154 @@ #include "openvic-simulation/population/PopValuesFromProvince.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/types/TypedIndices.hpp" -#include "openvic-simulation/types/TypedSpan.hpp" using namespace OpenVic; +static constexpr std::size_t VECTOR_COUNT = std::max( + GoodMarket::VECTORS_FOR_EXECUTE_ORDERS, + std::max( + CountryInstance::VECTORS_FOR_COUNTRY_TICK, + ProvinceInstance::VECTORS_FOR_PROVINCE_TICK + ) +); + +struct SectionContext { +public: + Date const& current_date; + std::span work_bundles; + MapInstance const& map_instance; + MilitaryDefines const& military_defines; + StaticModifierCache const& static_modifier_cache; + memory::FixedVector reusable_goods_mask; + memory::FixedVector reusable_country_map_0; + memory::FixedVector reusable_country_map_1; +private: + std::array, VECTOR_COUNT> _reusable_vectors; +public: + std::span, VECTOR_COUNT> reusable_vectors { _reusable_vectors }; + memory::vector reusable_good_index_vector; + PopValuesFromProvince reusable_pop_values; + + constexpr SectionContext( + Date const& current_date_ref, + std::span new_work_bundles, + ThreadDeps const& deps + ) : current_date { current_date_ref }, + work_bundles { new_work_bundles }, + map_instance { deps.map_instance }, + military_defines { deps.military_defines }, + static_modifier_cache { deps.static_modifier_cache }, + reusable_goods_mask { deps.good_count, {} }, + reusable_country_map_0 { deps.country_count, fixed_point_t::_0 }, + reusable_country_map_1 { deps.country_count, fixed_point_t::_0 }, + reusable_pop_values { + deps.game_rules_manager, + deps.good_instance_manager, + deps.modifier_effect_cache, + deps.production_type_manager, + deps.pop_defines, + deps.strata_count + } + {} +}; + +std::optional main_thread_ctx; + +static void process_section(const work_t work_type, SectionContext& ctx) { + for (WorkBundle& work_bundle : ctx.work_bundles) { + switch (work_type) { + case work_t::NONE: + break; + case work_t::GOOD_EXECUTE_ORDERS: + for (GoodMarket& good : work_bundle.goods_chunk) { + good.execute_orders( + ctx.reusable_country_map_0, + ctx.reusable_country_map_1, + ctx.reusable_vectors.first() + ); + } + break; + case work_t::PROVINCE_TICK: + for (ProvinceInstance& province : work_bundle.provinces_chunk) { + province.province_tick( + ctx.current_date, + ctx.reusable_pop_values, + work_bundle.random_number_generator, + ctx.reusable_goods_mask, + ctx.reusable_vectors.first() + ); + } + break; + case work_t::PROVINCE_INITIALISE_FOR_NEW_GAME: + for (ProvinceInstance& province : work_bundle.provinces_chunk) { + province.initialise_for_new_game( + ctx.current_date, + ctx.map_instance, + ctx.reusable_pop_values, + work_bundle.random_number_generator, + ctx.reusable_goods_mask, + ctx.reusable_vectors.first() + ); + } + break; + case work_t::COUNTRY_TICK_BEFORE_MAP: + for (CountryInstance& country : work_bundle.countries_chunk) { + country.country_tick_before_map( + ctx.reusable_goods_mask, + ctx.reusable_vectors.first(), + ctx.reusable_good_index_vector + ); + } + break; + case work_t::COUNTRY_TICK_AFTER_MAP: + for (CountryInstance& country : work_bundle.countries_chunk) { + country.country_tick_after_map(ctx.current_date); + } + break; + case work_t::PROVINCE_UPDATE_GAMESTATE: + for (ProvinceInstance& province : work_bundle.provinces_chunk) { + province.update_gamestate( + ctx.current_date, + ctx.military_defines + ); + } + break; + case work_t::COUNTRY_UPDATE_GAMESTATE_AFTER_MAP: + for (CountryInstance& country : work_bundle.countries_chunk) { + country.update_gamestate_after_map(ctx.current_date); + } + break; + case work_t::PROVINCE_UPDATE_MODIFIER_SUMS: + for (ProvinceInstance& province : work_bundle.provinces_chunk) { + province.update_modifier_sum( + ctx.current_date, + ctx.static_modifier_cache + ); + } + break; + case work_t::COUNTRY_UPDATE_MODIFIER_SUMS_BEFORE_MAP: + for (CountryInstance& country : work_bundle.countries_chunk) { + country.update_modifier_sum_before_map( + ctx.current_date, + ctx.static_modifier_cache + ); + } + break; + case work_t::COUNTRY_UPDATE_MODIFIER_SUMS_AFTER_MAP: + for (CountryInstance& country : work_bundle.countries_chunk) { + country.update_modifier_sum_after_map(ctx.current_date); + } + break; + } + } +} + void ThreadPool::loop_until_cancelled( work_t& work_type, const ThreadDeps deps, forwardable_span work_bundles ) { - memory::FixedVector reusable_goods_mask { deps.good_count, {} }; - - memory::FixedVector reusable_country_map_0 { deps.country_count, fixed_point_t::_0 }; - memory::FixedVector reusable_country_map_1 { deps.country_count, fixed_point_t::_0 }; - - static constexpr size_t VECTOR_COUNT = std::max( - GoodMarket::VECTORS_FOR_EXECUTE_ORDERS, - std::max( - CountryInstance::VECTORS_FOR_COUNTRY_TICK, - ProvinceInstance::VECTORS_FOR_PROVINCE_TICK - ) - ); - std::array, VECTOR_COUNT> reusable_vectors; - std::span, VECTOR_COUNT> reusable_vectors_span = std::span(reusable_vectors); - memory::vector reusable_good_index_vector; - PopValuesFromProvince reusable_pop_values { - deps.game_rules_manager, - deps.good_instance_manager, - deps.modifier_effect_cache, - deps.production_type_manager, - deps.pop_defines, - deps.strata_count - }; + SectionContext thread_ctx { current_date, work_bundles, deps }; while (!is_cancellation_requested) { work_t work_type_copy; @@ -60,92 +181,7 @@ void ThreadPool::loop_until_cancelled( work_type = work_t::NONE; } - for (WorkBundle& work_bundle : work_bundles) { - switch (work_type_copy) { - case work_t::NONE: - break; - case work_t::GOOD_EXECUTE_ORDERS: - for (GoodMarket& good : work_bundle.goods_chunk) { - good.execute_orders( - reusable_country_map_0, - reusable_country_map_1, - reusable_vectors_span.first() - ); - } - break; - case work_t::PROVINCE_TICK: - for (ProvinceInstance& province : work_bundle.provinces_chunk) { - province.province_tick( - current_date, - reusable_pop_values, - work_bundle.random_number_generator, - reusable_goods_mask, - reusable_vectors_span.first() - ); - } - break; - case work_t::PROVINCE_INITIALISE_FOR_NEW_GAME: - for (ProvinceInstance& province : work_bundle.provinces_chunk) { - province.initialise_for_new_game( - current_date, - deps.map_instance, - reusable_pop_values, - work_bundle.random_number_generator, - reusable_goods_mask, - reusable_vectors_span.first() - ); - } - break; - case work_t::COUNTRY_TICK_BEFORE_MAP: - for (CountryInstance& country : work_bundle.countries_chunk) { - country.country_tick_before_map( - reusable_goods_mask, - reusable_vectors_span.first(), - reusable_good_index_vector - ); - } - break; - case work_t::COUNTRY_TICK_AFTER_MAP: - for (CountryInstance& country : work_bundle.countries_chunk) { - country.country_tick_after_map(current_date); - } - break; - case work_t::PROVINCE_UPDATE_GAMESTATE: - for (ProvinceInstance& province : work_bundle.provinces_chunk) { - province.update_gamestate( - current_date, - deps.military_defines - ); - } - break; - case work_t::COUNTRY_UPDATE_GAMESTATE_AFTER_MAP: - for (CountryInstance& country : work_bundle.countries_chunk) { - country.update_gamestate_after_map(current_date); - } - break; - case work_t::PROVINCE_UPDATE_MODIFIER_SUMS: - for (ProvinceInstance& province : work_bundle.provinces_chunk) { - province.update_modifier_sum( - current_date, - deps.static_modifier_cache - ); - } - break; - case work_t::COUNTRY_UPDATE_MODIFIER_SUMS_BEFORE_MAP: - for (CountryInstance& country : work_bundle.countries_chunk) { - country.update_modifier_sum_before_map( - current_date, - deps.static_modifier_cache - ); - } - break; - case work_t::COUNTRY_UPDATE_MODIFIER_SUMS_AFTER_MAP: - for (CountryInstance& country : work_bundle.countries_chunk) { - country.update_modifier_sum_after_map(current_date); - } - break; - } - } + process_section(work_type_copy, thread_ctx); { std::lock_guard completed_lock { completed_mutex }; @@ -157,6 +193,11 @@ void ThreadPool::loop_until_cancelled( } void ThreadPool::process(const work_t work_type) { + if (!main_thread_ctx.has_value()) { + spdlog::error_s("ThreadPool isn't initialised."); + return; + } + { std::unique_lock thread_lock { thread_mutex }; if (is_cancellation_requested) { @@ -173,6 +214,7 @@ void ThreadPool::process(const work_t work_type) { } thread_condition.notify_all(); } + process_section(work_type, main_thread_ctx.value()); await_completion(); } @@ -206,7 +248,7 @@ void ThreadPool::initialise_threadpool( forwardable_span countries, forwardable_span provinces ) { - if (threads.size() > 0) { + if (main_thread_ctx.has_value()) { spdlog::error_s("Attempted to initialise ThreadPool again."); return; } @@ -221,14 +263,14 @@ void ThreadPool::initialise_threadpool( auto countries_begin = countries.begin(); auto provinces_begin = provinces.begin(); - for (size_t i = 0; i < WORK_BUNDLE_COUNT; i++) { - const size_t goods_chunk_size = i < goods_remainder + for (std::size_t i = 0; i < WORK_BUNDLE_COUNT; i++) { + const std::size_t goods_chunk_size = i < goods_remainder ? goods_quotient + 1 : goods_quotient; - const size_t countries_chunk_size = i < countries_remainder + const std::size_t countries_chunk_size = i < countries_remainder ? countries_quotient + 1 : countries_quotient; - const size_t provinces_chunk_size = i < provinces_remainder + const std::size_t provinces_chunk_size = i < provinces_remainder ? provinces_quotient + 1 : provinces_quotient; @@ -251,36 +293,53 @@ void ThreadPool::initialise_threadpool( provinces_begin = provinces_end; } - const size_t max_worker_threads = std::max(std::thread::hardware_concurrency(), 1); - threads.reserve(max_worker_threads); - work_per_thread.resize(max_worker_threads, work_t::NONE); - + const std::size_t max_worker_threads = std::min( + std::max(1, std::thread::hardware_concurrency()), + WORK_BUNDLE_COUNT + ); - const auto [work_bundles_quotient, work_bundles_remainder] = std::ldiv(WORK_BUNDLE_COUNT, max_worker_threads); - auto work_bundles_begin = all_work_bundles.begin(); + const auto [ + work_bundles_quotient, + work_bundles_remainder + ] = std::ldiv( + WORK_BUNDLE_COUNT, + max_worker_threads + ); + + const std::size_t extra_worker_thread_count = max_worker_threads - 1; // main thread also works + threads.reserve(extra_worker_thread_count); + work_per_thread.resize(extra_worker_thread_count, work_t::NONE); - for (size_t i = 0; i < max_worker_threads; ++i) { - const size_t work_bundles_chunk_size = i < work_bundles_remainder + auto work_bundles_begin = all_work_bundles.begin(); + for (std::size_t i = 0; i < max_worker_threads; ++i) { + const std::size_t work_bundles_chunk_size = i < work_bundles_remainder ? work_bundles_quotient + 1 : work_bundles_quotient; auto work_bundles_end = work_bundles_begin + work_bundles_chunk_size; - - threads.emplace_back( - [ - this, - &work_for_thread = work_per_thread[i], - deps, - work_bundles_begin, - work_bundles_end - ]() -> void { - loop_until_cancelled( - work_for_thread, + std::span work_bundles { work_bundles_begin, work_bundles_end }; + if (i < extra_worker_thread_count) { + threads.emplace_back( + [ + this, + &work_for_thread = work_per_thread[i], deps, - std::span{ work_bundles_begin, work_bundles_end } - ); - } - ); + work_bundles + ]() -> void { + loop_until_cancelled( + work_for_thread, + deps, + work_bundles + ); + } + ); + } else { + main_thread_ctx.emplace( + current_date, + work_bundles, + deps + ); + } work_bundles_begin = work_bundles_end; } diff --git a/src/openvic-simulation/utility/ThreadPool.hpp b/src/openvic-simulation/utility/ThreadPool.hpp index 5bf834067..895da157e 100644 --- a/src/openvic-simulation/utility/ThreadPool.hpp +++ b/src/openvic-simulation/utility/ThreadPool.hpp @@ -5,7 +5,6 @@ #include #include -#include "openvic-simulation/types/Date.hpp" #include "openvic-simulation/core/memory/Vector.hpp" #include "openvic-simulation/core/portable/ForwardableSpan.hpp" #include "openvic-simulation/core/random/RandomGenerator.hpp" @@ -65,8 +64,6 @@ namespace OpenVic { std::atomic active_work_count = 0; bool is_cancellation_requested = false; Date const& current_date; - bool is_good_execute_orders_requested = false; - bool is_province_tick_requested = false; void loop_until_cancelled( work_t& work_type,