diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 53d5ddc3d42..02e63d01ea8 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -6889,6 +6889,14 @@ bool monk_t::validate_fight_style( fight_style_e style ) const return true; } +void monk_t::init() +{ + base_t::init(); + + sim_controller_t::register_sim_controller( sim, this, STAT_CRIT_RATING, 100.0 ); + sim_controller_t::register_sim_controller( sim, this, TWW2, B2 ); +} + // monk_t::init_spells ====================================================== void monk_t::init_spells() { diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index 5c730300b43..03af810b65e 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -16,6 +16,7 @@ #include "sc_enums.hpp" #include "sc_stagger.hpp" #include "sim/proc.hpp" +#include "sim/sim_controller.hpp" #include "util/timeline.hpp" #include @@ -1476,6 +1477,7 @@ struct monk_t : public stagger_t bool validate_fight_style( fight_style_e style ) const override; // Init / Reset + void init() override; void create_pets() override; void init_spells() override; void init_background_actions() override; diff --git a/engine/report/json/report_json.cpp b/engine/report/json/report_json.cpp index 5a25c85d0d6..0f50e3f174f 100644 --- a/engine/report/json/report_json.cpp +++ b/engine/report/json/report_json.cpp @@ -1047,6 +1047,11 @@ void profileset_json2( const profileset::profilesets_t& profileset, const sim_t& } } + // report source, location, and reason of interrupt for + // all registered profileset sim controllers + // for ( const auto& controller: sim.sim_controllers ) + // controller->report_json_profileset( obj ); + // Optional override ouput data if ( !sim.profileset_output_data.empty() ) { @@ -1092,6 +1097,12 @@ void profileset_json3( const profileset::profilesets_t& profilesets, const sim_t obj[ "iterations" ] = as( result.iterations() ); } + // report source, location, and reason of interrupt for + // all registered profileset sim controllers + // for ( const auto& controller: sim.sim_controllers ) + // controller->report_json_profileset( obj ); + + // Optional override ouput data if ( !sim.profileset_output_data.empty() ) { diff --git a/engine/report/report_html_sim.cpp b/engine/report/report_html_sim.cpp index b1c0bd7520d..3bdafdb5b73 100644 --- a/engine/report/report_html_sim.cpp +++ b/engine/report/report_html_sim.cpp @@ -1160,6 +1160,29 @@ void print_profilesets( std::ostream& out, const profileset::profilesets_t& prof print_profilesets_chart( out, sim ); + + if ( sim.sim_controller_data.size() ) + { + out << "

Profileset Sim Control

\n"; + out << "
\n"; + + out << "
Sim Controllers
    \n"; + for ( const auto& [ key, controller_data ] : sim.sim_controller_data ) + controller_data.report_html_options( out ); + out << "
\n"; + + // report source, location, and reason of interrupt for + // all registered profileset sim controllers + // TODO: check if any are culled, otherwise omit table + out << "
Interrupted Profilesets
    \n"; + for ( const auto& [ key, controller_data ] : sim.sim_controller_data ) + if ( controller_data.exit_reasons.size() ) + controller_data.report_html_profileset( out ); + out << "
\n"; + out << "
"; + } + + out << ""; out << ""; } diff --git a/engine/sim/sim.cpp b/engine/sim/sim.cpp index 095c93a8e17..c87e153a74f 100644 --- a/engine/sim/sim.cpp +++ b/engine/sim/sim.cpp @@ -1546,6 +1546,8 @@ sim_t::sim_t() count_overheal_as_heal( false ), scaling_normalized( 1.0 ), merge_enemy_priority_dmg( false ), + sim_controllers(), + sim_controller_data(), // Multi-Threading threads( 0 ), thread_index( 0 ), @@ -3059,6 +3061,8 @@ bool sim_t::iterate() progress_bar.init(); + sim_controller_t::evaluate( this, sim_controller_t::POST_INIT ); + try { activate_actors(); @@ -3076,6 +3080,8 @@ bool sim_t::iterate() progress_bar.output( false ); } + sim_controller_t::evaluate( this, sim_controller_t::POST_ITER ); + do_pause(); auto old_active = current_index; if ( !canceled ) diff --git a/engine/sim/sim.hpp b/engine/sim/sim.hpp index d449957600d..6768ba36c17 100644 --- a/engine/sim/sim.hpp +++ b/engine/sim/sim.hpp @@ -11,6 +11,7 @@ #include "progress_bar.hpp" #include "sim_ostream.hpp" #include "sim/option.hpp" +#include "interfaces/sc_js.hpp" #include "util/concurrency.hpp" #include "util/rng.hpp" #include "util/sample_data.hpp" @@ -19,6 +20,7 @@ #include #include +#include struct actor_target_data_t; struct buff_t; @@ -49,6 +51,114 @@ namespace profileset{ class profilesets_t; } +struct sim_controller_data_t; +template +struct data_wrapper_t +{ + T& data; + + data_wrapper_t( T& data, std::recursive_mutex& m ) : data( data ), lock( m ) + { + } + +private: + std::scoped_lock lock; +}; + +struct exit_reason_t; +struct sim_controller_data_wrapper_t +{ + std::recursive_mutex mutex; + std::unique_ptr data; + std::vector exit_reasons; + std::vector> options; + + sim_controller_data_wrapper_t(); + sim_controller_data_wrapper_t( std::unique_ptr&& data ); + + ~sim_controller_data_wrapper_t() = default; + + void report_json_profileset( js::JsonOutput& ) const; + void report_json_options( js::JsonOutput& ) const; + void report_html_profileset( std::ostream& ) const; + void report_html_options( std::ostream& ) const; + + // disallow copy, as that would introduce additional mutexes for a single controller name + sim_controller_data_wrapper_t( const sim_controller_data_wrapper_t& ) = delete; +}; + +// local profileset data and methods +struct sim_controller_t +{ + using data_t = sim_controller_data_t; + + enum call_point_e + { + NONE, + POST_INIT, + POST_ITER + }; + + static const std::string call_point_string( call_point_e call_point ); + static void evaluate( sim_t* sim, call_point_e call_point ); + template , bool>> + static bool register_sim_controller( sim_t* sim, Args&&... args ); + + sim_t* parent; + sim_t* sim; + + sim_controller_t( sim_t* sim ); + virtual ~sim_controller_t() = default; + + sim_controller_t( sim_controller_t& ) = delete; + sim_controller_t( const sim_controller_t& ) = delete; + + const std::string message( call_point_e ); + void add_option( std::unique_ptr ); + + virtual const std::string name() const = 0; + virtual const std::string reason() const = 0; + virtual void create_options() {} + + virtual bool evaluate_post_init() + { + return true; + } + + virtual bool evaluate_post_iter() + { + return true; + } + +protected: + template + data_wrapper_t get_data(); + template + void set_data( T&& data ); +}; + +struct exit_reason_t +{ + const std::string profileset_name; + const sim_controller_t::call_point_e exit_point; + const std::string exit_reason; + + exit_reason_t( const std::string profileset_name, const sim_controller_t::call_point_e exit_point, const std::string exit_reason ) + : profileset_name( profileset_name ), exit_point( exit_point ), exit_reason( exit_reason ) + {} +}; + +// global profileset data to be shared across all instantiations of a derived `sim_controller_t` +struct sim_controller_data_t +{ + sim_controller_data_t(); + sim_controller_data_t( sim_controller_data_t& data ); + sim_controller_data_t( const sim_controller_data_t& ) = delete; + + virtual ~sim_controller_data_t() = default; +}; + struct sim_progress_t { int current_iterations; @@ -622,6 +732,11 @@ struct sim_t : private sc_thread_t double scaling_normalized; bool merge_enemy_priority_dmg; + // sim control + std::vector> sim_controllers; + std::map sim_controller_data; + +public: // Multi-Threading mutex_t merge_mutex; int threads; @@ -796,6 +911,7 @@ struct sim_t : private sc_thread_t { return _rng; } double averaged_range( double min, double max ); + // Thread id of this sim_t object #ifndef SC_NO_THREADING std::thread::id thread_id() const diff --git a/engine/sim/sim_controller.cpp b/engine/sim/sim_controller.cpp new file mode 100644 index 00000000000..33631bf19bb --- /dev/null +++ b/engine/sim/sim_controller.cpp @@ -0,0 +1,164 @@ +#include "sim_controller.hpp" + +#include "sc_enums.hpp" +#include "player/set_bonus.hpp" +#include "profileset.hpp" +#include "sim.hpp" + +sim_controller_data_wrapper_t::sim_controller_data_wrapper_t() : mutex(), data(), exit_reasons() +{ +} + +sim_controller_data_wrapper_t::sim_controller_data_wrapper_t( std::unique_ptr&& data ) + : mutex(), data( std::move( data ) ), exit_reasons() +{ +} + +sim_controller_data_t::sim_controller_data_t() +{ +} + +sim_controller_data_t::sim_controller_data_t( sim_controller_data_t& ) +{ +} + +sim_controller_t::sim_controller_t( sim_t* sim ) + : parent( sim->parent ), sim( sim ) +{ + assert( sim ); + assert( sim->parent ); +} + +const std::string sim_controller_t::call_point_string( call_point_e call_point ) +{ + switch ( call_point ) + { + case POST_INIT: + return "simulation initialization"; + case POST_ITER: + return "iteration"; + default: + assert( false ); + return "no matching call point"; + } +} + +void sim_controller_t::evaluate( sim_t* sim, call_point_e call_point ) +{ + if ( !sim->profileset_enabled || !sim->parent ) + return; + + typedef std::unique_ptr sc_ptr_t; + std::function cb; + switch ( call_point ) + { + case POST_INIT: + cb = []( sc_ptr_t& sc ) { return !sc->evaluate_post_init(); }; + break; + case POST_ITER: + cb = []( sc_ptr_t& sc ) { return !sc->evaluate_post_iter(); }; + break; + default: + assert( false ); + break; + } + auto sc = range::find_if( sim->sim_controllers, cb ); + if ( sc == sim->sim_controllers.end() ) + return; + + auto* controller = sc->get(); + assert( controller->sim == sim ); + assert( controller->parent == sim->parent ); + + auto& scd = sim->parent->sim_controller_data.at( controller->name() ); + std::scoped_lock L( scd.mutex ); + + scd.exit_reasons.emplace_back( sim->parent->profilesets->current_profileset_name(), call_point, + controller->reason() ); + + sim->canceled = true; + sim->error( error_level_e::TRIVIAL, "{}", controller->message( call_point ) ); + sim->interrupt(); +} + +const std::string sim_controller_t::message( call_point_e call_point ) +{ + std::string msg = + fmt::format( "Profileset {} was canceled by {} after {}", parent->profilesets->current_profileset_name(), name(), + call_point_string( call_point ) ); + if ( call_point == POST_ITER ) + msg += std::to_string( sim->current_iteration ); + + if ( const std::string r = reason(); r != "" ) + msg += fmt::format( " because {}.", r ); + else + msg += "."; + + return msg; +} + +void sim_controller_t::add_option( std::unique_ptr option ) +{ +} + +void sim_controller_data_wrapper_t::report_json_profileset( js::JsonOutput& output ) const +{ + for ( const exit_reason_t& exit_reason : exit_reasons ) + { + output[ "interrupted_by" ] = exit_reason.profileset_name; + output[ "exit_point" ] = sim_controller_t::call_point_string( exit_reason.exit_point ); + output[ "exit_reason" ] = exit_reason.exit_reason; + } +} + +void sim_controller_data_wrapper_t::report_json_options( js::JsonOutput& ) const +{ + // TODO: implement opt parsing and automatic generation of report json from opts +} + +void sim_controller_data_wrapper_t::report_html_profileset( std::ostream& output ) const +{ + for ( const exit_reason_t& exit_reason : exit_reasons ) + output << "
  • " + << util::encode_html( exit_reason.profileset_name ) << " " + << util::encode_html( sim_controller_t::call_point_string( exit_reason.exit_point ) ) << " " + << util::encode_html( exit_reason.exit_reason ) + << "
  • "; +} + +void sim_controller_data_wrapper_t::report_html_options( std::ostream& ) const +{} + +min_player_stat_t::min_player_stat_t( sim_t* sim, player_t* target_player, stat_e rating, double amount ) + : sim_controller_t( sim ), target_player( target_player ), rating( rating ), min_rating( amount ) +{ +} + +bool min_player_stat_t::evaluate_post_init() +{ + return true; +} + +const std::string min_player_stat_t::reason() const +{ + return fmt::format( "player {} does not exceed {} rating for {}", target_player->name(), + min_rating, util::stat_type_string( rating ) ); +} + +tier_set_count_t::tier_set_count_t( sim_t* sim, player_t* target_player, set_bonus_type_e tier, set_bonus_e count ) + : sim_controller_t( sim ), target_player( target_player ), tier( tier ), count( count ) +{ +} + +bool tier_set_count_t::evaluate_post_init() +{ + return target_player->sets->has_set_bonus( target_player->specialization(), tier, count ); +} + +const std::string tier_set_count_t::reason() const +{ + // no to string for set bonus tier or count... + // that should definitely exist :) + return fmt::format( "player {} does not have tier {} {} active", target_player->name(), static_cast( tier ), + static_cast( count ) ); +} diff --git a/engine/sim/sim_controller.hpp b/engine/sim/sim_controller.hpp new file mode 100644 index 00000000000..947a2b69b41 --- /dev/null +++ b/engine/sim/sim_controller.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include "player/player.hpp" +#include "player/rating.hpp" +#include "sc_enums.hpp" +#include "sim.hpp" + +template +bool sim_controller_t::register_sim_controller( sim_t* sim, Args&&... args ) +{ + if ( sim && sim->profileset_enabled && sim->parent ) + { + sim->sim_controllers.emplace_back( std::make_unique( sim, std::forward( args )... ) ); + return sim->parent->sim_controller_data + .emplace( sim->sim_controllers.back()->name(), std::make_unique() ) + .second; + } + return false; +} + +template +data_wrapper_t sim_controller_t::get_data() +{ + auto& data = parent->sim_controller_data.at( name() ); + return { *std::static_pointer_cast( data.data ), data.mutex }; +} + +template +void sim_controller_t::set_data( T&& data ) +{ + auto& scd = parent->sim_controller_data; + assert( scd.find( name() ) != scd.end() ); + scd[ name() ].data = std::make_unique( data ); +} + +struct min_player_stat_t : sim_controller_t +{ + /* + * This sim controller doesn't work, as at all controller evaluation points + * only have base rating provided by the class/spec. If gear stats were to + * be set once on actor init and preserved between iterations, this would be + * fixed. + */ + using data_t = sim_controller_data_t; + + player_t* target_player; + stat_e rating; + double min_rating; + + min_player_stat_t( sim_t*, player_t*, stat_e, double ); + const std::string name() const override + { + return "min_player_stat"; + } + bool evaluate_post_init() override; + const std::string reason() const override; +}; + +struct tier_set_count_t : sim_controller_t +{ + using data_t = sim_controller_data_t; + + player_t* target_player; + set_bonus_type_e tier; + set_bonus_e count; + + tier_set_count_t( sim_t*, player_t*, set_bonus_type_e, set_bonus_e ); + const std::string name() const override + { + return "tier_set_count"; + } + bool evaluate_post_init() override; + const std::string reason() const override; +}; diff --git a/source_files/QT_engine.pri b/source_files/QT_engine.pri index ec8f083c5c1..2b8ff4f5293 100644 --- a/source_files/QT_engine.pri +++ b/source_files/QT_engine.pri @@ -171,6 +171,7 @@ HEADERS += engine/sim/reforge_plot.hpp HEADERS += engine/sim/scale_factor_control.hpp HEADERS += engine/sim/sim.hpp HEADERS += engine/sim/sim_control.hpp +HEADERS += engine/sim/sim_controller.hpp HEADERS += engine/sim/sim_ostream.hpp HEADERS += engine/sim/uptime.hpp HEADERS += engine/sim/work_queue.hpp @@ -366,6 +367,7 @@ SOURCES += engine/sim/raid_event.cpp SOURCES += engine/sim/reforge_plot.cpp SOURCES += engine/sim/scale_factor_control.cpp SOURCES += engine/sim/sim.cpp +SOURCES += engine/sim/sim_controller.cpp SOURCES += engine/sim/sim_ostream.cpp SOURCES += engine/sim/uptime_benefit.cpp SOURCES += engine/util/cache.cpp diff --git a/source_files/VS_engine.props b/source_files/VS_engine.props index ad67be4033c..44a98da242c 100644 --- a/source_files/VS_engine.props +++ b/source_files/VS_engine.props @@ -175,6 +175,7 @@ To change the list of source files run synchronize.py + @@ -369,6 +370,7 @@ To change the list of source files run synchronize.py + diff --git a/source_files/cmake_engine.txt b/source_files/cmake_engine.txt index 6f99f9d9d94..0845f9d2dde 100644 --- a/source_files/cmake_engine.txt +++ b/source_files/cmake_engine.txt @@ -169,6 +169,7 @@ sim/reforge_plot.hpp sim/scale_factor_control.hpp sim/sim.hpp sim/sim_control.hpp +sim/sim_controller.hpp sim/sim_ostream.hpp sim/uptime.hpp sim/work_queue.hpp @@ -363,6 +364,7 @@ sim/raid_event.cpp sim/reforge_plot.cpp sim/scale_factor_control.cpp sim/sim.cpp +sim/sim_controller.cpp sim/sim_ostream.cpp sim/uptime_benefit.cpp util/cache.cpp diff --git a/source_files/engine_make b/source_files/engine_make index 862193bab57..a069441936e 100644 --- a/source_files/engine_make +++ b/source_files/engine_make @@ -168,6 +168,7 @@ SRC += \ sim$(PATHSEP)reforge_plot.cpp \ sim$(PATHSEP)scale_factor_control.cpp \ sim$(PATHSEP)sim.cpp \ + sim$(PATHSEP)sim_controller.cpp \ sim$(PATHSEP)sim_ostream.cpp \ sim$(PATHSEP)uptime_benefit.cpp \ util$(PATHSEP)cache.cpp \