diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 53d5ddc3d42..61e3bdd5e81 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -36,6 +36,7 @@ #include "report/charts.hpp" #include "report/highchart.hpp" #include "sc_enums.hpp" +#include "sim/profileset_control.hpp" #include @@ -4346,7 +4347,7 @@ struct xuen_spell_t : public monk_spell_t cast_during_sck = true; // Specifically set for 10.1 class trinket - harmful = true; + harmful = true; } void execute() override @@ -4577,7 +4578,7 @@ struct niuzao_spell_t : public monk_spell_t // Specifically set for 10.1 class trinket harmful = true; // Forcing the minimum GCD to 750 milliseconds - min_gcd = timespan_t::from_millis( 750 ); + min_gcd = timespan_t::from_millis( 750 ); apply_affecting_aura( p->talent.brewmaster.walk_with_the_ox ); } @@ -4630,7 +4631,7 @@ struct chiji_spell_t : public monk_spell_t // Specifically set for 10.1 class trinket harmful = true; // Forcing the minimum GCD to 750 milliseconds - min_gcd = timespan_t::from_millis( 750 ); + min_gcd = timespan_t::from_millis( 750 ); } void execute() override @@ -4659,7 +4660,7 @@ struct yulon_spell_t : public monk_spell_t // Specifically set for 10.1 class trinket harmful = true; // Forcing the minimum GCD to 750 milliseconds - min_gcd = timespan_t::from_millis( 750 ); + min_gcd = timespan_t::from_millis( 750 ); } void execute() override diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index 5c730300b43..6c7dc799573 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/profileset_control.hpp" #include "util/timeline.hpp" #include diff --git a/engine/report/json/report_json.cpp b/engine/report/json/report_json.cpp index 5a25c85d0d6..c3bf0f91d07 100644 --- a/engine/report/json/report_json.cpp +++ b/engine/report/json/report_json.cpp @@ -1279,6 +1279,7 @@ void to_json( const ::report::json::report_configuration_t& report_configuration { auto profileset_root = root[ "profilesets" ]; profileset_json( report_configuration, *sim.profilesets, sim, profileset_root ); + profileset_controller::report_json( sim, root ); } if ( !sim.plot->dps_plot_stats.empty() ) diff --git a/engine/report/report_html_sim.cpp b/engine/report/report_html_sim.cpp index b1c0bd7520d..126de5a523d 100644 --- a/engine/report/report_html_sim.cpp +++ b/engine/report/report_html_sim.cpp @@ -1160,6 +1160,8 @@ void print_profilesets( std::ostream& out, const profileset::profilesets_t& prof print_profilesets_chart( out, sim ); + profileset_controller::report_html( sim, out ); + out << ""; out << ""; } diff --git a/engine/sim/profileset_control.cpp b/engine/sim/profileset_control.cpp new file mode 100644 index 00000000000..ed1517ca525 --- /dev/null +++ b/engine/sim/profileset_control.cpp @@ -0,0 +1,336 @@ +#include "profileset_control.hpp" + +#include "dbc/dbc.hpp" +#include "dbc/item_set_bonus.hpp" +#include "player/set_bonus.hpp" +#include "profileset.hpp" +#include "sc_enums.hpp" +#include "sim.hpp" + +std::unordered_map profileset_controller_t::factory = { + { "set_bonus_enabled", profileset_controller::create_fn_pair() } }; + +std::atomic_uint profileset_controller_data_wrapper_t::id_generator; + +profileset_controller_data_t::profileset_controller_data_t( std::string_view key, std::string_view options ) + : key( key ), options( options ) +{ +} + +void profileset_controller_data_t::report_html_options( std::ostream& output ) const +{ + output << "" + << "" << util::encode_html( key ) << "" + << "" << exit_reasons.size() << "" + << "" << util::encode_html( options ) << "" + << "\n"; +} + +void profileset_controller_data_t::report_html_profileset( std::ostream& output ) const +{ + bool first = true; + output << fmt::format( "{}", exit_reasons.size(), + util::encode_html( key ) ); + for ( const auto& [ name, call_point, reason ] : exit_reasons ) + { + if ( !first ) + output << ""; + output << "" << util::encode_html( name ) << "" + << "" << util::encode_html( profileset_controller::call_point_string( call_point ) ) << "" + << "" << util::encode_html( reason ) << "" + << "\n"; + first = false; + } +} + +void profileset_controller_data_t::report_json_options( js::JsonOutput& root ) const +{ + auto output = root.add(); + auto splits = util::string_split( options, "," ); + output[ "profileset_controller_name" ] = key; + for ( const auto& split : splits ) + { + auto subsplit = util::string_split( split, "=" ); + assert( subsplit.size() == 2 ); + output[ subsplit[ 0 ] ] = subsplit[ 1 ]; + } +} + +void profileset_controller_data_t::report_json_profileset( js::JsonOutput& root ) const +{ + for ( const auto& [ name, call_point, reason ] : exit_reasons ) + { + auto output = root.add(); + output[ "profileset_name" ] = name; + output[ "interrupted_by" ] = key; + output[ "exit_point" ] = profileset_controller::call_point_string( call_point ); + output[ "exit_reason" ] = reason; + } +} + +profileset_controller_data_wrapper_t::profileset_controller_data_wrapper_t( std::string key, std::string_view options ) + : mutex(), id( id_generator++ ), key( key ), options( options ) +{ + if ( const auto& value = profileset_controller_t::factory.find( key ); + value != profileset_controller_t::factory.end() ) + data = value->second.second( key, options ); + assert( data ); +} + +void profileset_controller_data_wrapper_t::construct_controller( sim_t* sim ) +{ + if ( const auto& value = profileset_controller_t::factory.find( key ); + value != profileset_controller_t::factory.end() ) + { + auto controller = value->second.first( sim, id ); + controller->create_options(); + opts::parse( sim, "profileset_controller", controller->options, options, + [ this, &sim ]( opts::parse_status status, util::string_view name, util::string_view value ) { + // Fail parsing if strict parsing is used and the option is not found + if ( sim->strict_parsing && status == opts::parse_status::NOT_FOUND ) + return opts::parse_status::FAILURE; + // .. otherwise, just warn that there's an unknown option + if ( status == opts::parse_status::NOT_FOUND ) + sim->error( + "Warning: profileset controller '{}' provided unknown option '{}' with value '{}', ignoring.", + key, name, value ); + return status; + } ); + sim->profileset_controller.emplace_back( std::move( controller ) ); + return; + } + assert( false && "No factory fn for key found." ); +} + +bool profileset_controller_t::register_controller( std::string key, profileset_controller_t::factory_fn_pair_t&& value ) +{ + return factory.try_emplace( key, std::move( value ) ).second; +} + +bool profileset_controller_t::controller_exists( std::string key ) +{ + return factory.find( key ) != factory.end(); +} + +void profileset_controller_t::evaluate( sim_t* sim, call_point_e call_point ) +{ + if ( !sim->profileset_enabled || !sim->parent ) + return; + + std::function& )> cb; + switch ( call_point ) + { + case POST_INIT: + cb = []( std::unique_ptr& sc ) { return !sc->evaluate_post_init(); }; + break; + case POST_ITER: + cb = []( std::unique_ptr& sc ) { return !sc->evaluate_post_iter(); }; + break; + default: + assert( false ); + break; + } + auto pc = range::find_if( sim->profileset_controller, cb ); + if ( pc == sim->profileset_controller.end() ) + return; + + auto controller = pc->get(); + assert( controller->sim == sim ); + assert( controller->parent == sim->parent ); + + controller->set_exit_reason( + { 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(); +} + +void profileset_controller_t::add_option( std::unique_ptr&& option ) +{ + options.emplace_back( std::move( option ) ); +} + +profileset_controller_t::profileset_controller_t( sim_t* sim, unsigned int id ) + : parent( sim->parent ), sim( sim ), id( id ) +{ + assert( sim && sim->parent ); +} + +const std::string profileset_controller_t::message( call_point_e call_point ) +{ + std::string msg = fmt::format( "Profileset {} was canceled by Profileset Controller {} after {}", + parent->profilesets->current_profileset_name(), name(), + profileset_controller::call_point_string( call_point ) ); + if ( call_point == POST_ITER ) + msg += std::to_string( sim->current_iteration ); + + if ( const auto r = reason(); !r.empty() ) + msg += fmt::format( " because {}.", r ); + else + msg += "."; + + return msg; +} + +void profileset_controller_t::set_exit_reason( exit_reason_t&& exit_reason ) +{ + auto& pcd = parent->profileset_controller_data; + assert( pcd.size() > id ); + pcd[ id ].data->exit_reasons.emplace_back( std::move( exit_reason ) ); +} + +namespace +{ +// how to do this with reference wrapper instead of template? +template +void report_html_table( + std::ostream& out, std::vector keys, const std::deque& data, + T ref, std::function& )> cond = []( const auto& ) { + return true; + } ) +{ + out << "\n" + << ""; + bool first = true; + for ( const auto& key : keys ) + { + out << fmt::format( ""; + first = false; + } + out << "\n"; + for ( const auto& datum_wrapper : data ) + if ( const auto& datum = datum_wrapper.data; datum && cond( datum ) ) + std::invoke( ref, datum, out ); + out << "
", first ? "left" : "center" ) << key << "
"; +} +} // namespace + +namespace profileset_controller +{ +const std::string 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 report_html( const sim_t& sim, std::ostream& out ) +{ + if ( sim.profileset_controller_data.empty() ) + return; + + out << "

Profileset Sim Control

\n"; + out << "
\n"; + + out << "
Profileset Controllers\n"; + report_html_table( out, { "Type", "Count", "Options" }, sim.profileset_controller_data, + &profileset_controller_data_t::report_html_options ); + out << "
\n"; + + // report source, location, and reason of interrupt for + // all registered profileset profileset controllers + bool has_culled_profileset = range::any_of( sim.profileset_controller_data, + []( const auto& datum ) { return datum.data->exit_reasons.size(); } ); + + if ( has_culled_profileset ) + { + out << "
Cancelled Profilesets\n"; + report_html_table( out, { "Type", "Profileset Name", "Cancellation Point", "Reason" }, + sim.profileset_controller_data, &profileset_controller_data_t::report_html_profileset, + []( const auto& datum ) { return datum->exit_reasons.size(); } ); + out << "
\n"; + } + out << "
"; +} + +void report_json( const sim_t& sim, js::JsonOutput& output ) +{ + if ( sim.profileset_controller_data.empty() ) + return; + + auto root = output[ "profileset_controller" ]; + + auto exits = root[ "cancelled_profilesets" ].make_array(); + for ( const auto& datum_wrapper : sim.profileset_controller_data ) + if ( const auto& datum = datum_wrapper.data; datum ) + datum->report_json_profileset( exits ); + + auto controllers = root[ "enabled_controllers" ].make_array(); + for ( const auto& datum_wrapper : sim.profileset_controller_data ) + if ( const auto& datum = datum_wrapper.data; datum ) + datum->report_json_options( controllers ); +} +} // namespace profileset_controller + +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 ) ); +} + +bool set_bonus_enabled_t::evaluate_post_init() +{ + if ( target_player ) + return target_player->sets->has_set_bonus( target_player->specialization(), tier, count ); + return true; +} + +const std::string set_bonus_enabled_t::reason() const +{ + // no to string for set bonus tier or count... + // that should definitely exist :) + auto set_bonuses = item_set_bonus_t::data( target_player ? target_player->dbc->ptr : false ); + std::string tier_name{}; + for ( const auto& set_bonus : set_bonuses ) + if ( set_bonus.enum_id == static_cast( tier ) ) + tier_name = set_bonus.tier; + return fmt::format( "player {} does not have set {} {}pc active", target_player->name(), tier_name, + static_cast( count + 1 ) ); +} + +void set_bonus_enabled_t::create_options() +{ + add_option( opt_func( "tier", [ this ]( sim_t*, util::string_view, util::string_view value ) { + auto set_bonuses = item_set_bonus_t::data( target_player ? target_player->dbc->ptr : false ); + for ( const auto& set_bonus : set_bonuses ) + { + if ( util::str_compare_ci( set_bonus.tier, value ) ) + { + this->tier = static_cast( set_bonus.enum_id ); + return true; + } + } + return false; + } ) ); + add_option( opt_func( "pc", [ this ]( sim_t*, util::string_view, util::string_view value ) { + auto bonus_value = util::to_unsigned( value ); + if ( bonus_value > B_MAX ) + return false; + this->count = static_cast( bonus_value - 1 ); + return true; + } ) ); + add_option( opt_func( "player", [ this ]( sim_t* sim, util::string_view, util::string_view value ) { + for ( auto& player : sim->player_list ) + { + if ( util::str_compare_ci( player->name(), value ) ) + { + this->target_player = player; + return true; + } + } + return false; + } ) ); +} diff --git a/engine/sim/profileset_control.hpp b/engine/sim/profileset_control.hpp new file mode 100644 index 00000000000..410bbafd3ec --- /dev/null +++ b/engine/sim/profileset_control.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include "player/player.hpp" +#include "player/rating.hpp" +#include "sc_enums.hpp" +#include "util/generic.hpp" + +#include +#include +#include + +/* + * TODO: + * - implement profileset culling specialization + */ + +struct sim_t; + +enum call_point_e +{ + CALL_POINT_NONE, + POST_INIT, + POST_ITER +}; + +template +struct data_wrapper_t +{ +private: + std::scoped_lock lock; + +public: + const T& data; + + data_wrapper_t( const T& data, std::recursive_mutex& m ) : lock( m ), data( data ) + { + } +}; + +struct exit_reason_t +{ + const std::string profileset_name; + const call_point_e exit_point; + const std::string exit_reason; +}; + +struct profileset_controller_data_t : private noncopyable +{ + const std::string key; + std::string_view options; + std::vector exit_reasons; + + profileset_controller_data_t( std::string_view, std::string_view ); + virtual ~profileset_controller_data_t() = default; + + virtual void report_html_options( std::ostream& ) const; + virtual void report_html_profileset( std::ostream& ) const; + virtual void report_json_options( js::JsonOutput& ) const; + virtual void report_json_profileset( js::JsonOutput& ) const; +}; + +struct profileset_controller_data_wrapper_t : private noncopyable +{ +private: + static std::atomic_uint id_generator; + +public: + std::recursive_mutex mutex; + + const unsigned int id; + const std::string key; + std::string_view options; + std::unique_ptr data; + + profileset_controller_data_wrapper_t( std::string, std::string_view ); + + void construct_controller( sim_t* ); +}; + +struct profileset_controller_t : private noncopyable +{ + using controller_factory_t = std::function( sim_t*, unsigned int )>; + using data_factory_t = + std::function( std::string_view, std::string_view )>; + using factory_fn_pair_t = std::pair; + +protected: + friend profileset_controller_data_wrapper_t; + static std::unordered_map factory; + +public: + static bool register_controller( std::string, factory_fn_pair_t&& ); + static bool controller_exists( std::string ); + + using data_t = profileset_controller_data_t; + static void evaluate( sim_t* sim, call_point_e call_point ); + + sim_t* parent; + sim_t* sim; + const unsigned int id; + std::vector> options; + + profileset_controller_t( sim_t*, unsigned int ); + virtual ~profileset_controller_t() = default; + + 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 ); + void set_exit_reason( exit_reason_t&& ); +}; + +namespace profileset_controller +{ +const std::string call_point_string( call_point_e call_point ); +void report_html( const sim_t&, std::ostream& ); +void report_json( const sim_t&, js::JsonOutput& output ); + +template +profileset_controller_t::factory_fn_pair_t create_fn_pair() +{ + return { + []( sim_t* sim, unsigned int id ) { return std::make_unique( sim, id ); }, + []( std::string_view key, std::string_view options ) { return std::make_unique( key, options ); } }; +} +}; // namespace profileset_controller + +struct min_player_stat_t : profileset_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 = profileset_controller_data_t; + + player_t* target_player; + stat_e rating; + double min_rating; + + const std::string name() const override + { + return "min_player_stat"; + } + bool evaluate_post_init() override; + const std::string reason() const override; +}; + +struct set_bonus_enabled_t : profileset_controller_t +{ + using data_t = profileset_controller_data_t; + + player_t* target_player; + set_bonus_type_e tier; + set_bonus_e count; + + set_bonus_enabled_t( sim_t* sim, unsigned int id ) : profileset_controller_t( sim, id ) + { + } + + const std::string name() const override + { + return "set_bonus_enabled"; + } + bool evaluate_post_init() override; + const std::string reason() const override; + void create_options() override; +}; diff --git a/engine/sim/sim.cpp b/engine/sim/sim.cpp index 095c93a8e17..552f62b7fee 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 ), + profileset_controller(), + profileset_controller_data(), // Multi-Threading threads( 0 ), thread_index( 0 ), @@ -2948,6 +2950,21 @@ void sim_t::init() plot->initialize(); } + if ( !parent && !profileset_controller_options.empty() && !profileset_map.empty() ) + { + for ( const auto& [ key, values ] : profileset_controller_options ) + { + if ( profileset_controller_t::controller_exists( key ) ) + for ( const auto& value : values ) + profileset_controller_data.emplace_back( key, value ); + else + throw sc_invalid_sim_argument( fmt::format( "Unknown profileset controller option with name '{}'.", key ) ); + } + } + if ( parent && profileset_enabled ) + for ( auto& profileset_controller_datum : parent->profileset_controller_data ) + profileset_controller_datum.construct_controller( this ); + initialized = true; init_time = chrono::elapsed(start_time); @@ -2979,7 +2996,7 @@ void sim_t::analyze() std::fflush( stdout ); } - + assert( iterations > 0 ); // Run core analyze for all actor collected data before proceeding to full analysis. This is to prevent errors from @@ -3059,6 +3076,8 @@ bool sim_t::iterate() progress_bar.init(); + profileset_controller_t::evaluate( this, POST_INIT ); + try { activate_actors(); @@ -3076,6 +3095,8 @@ bool sim_t::iterate() progress_bar.output( false ); } + profileset_controller_t::evaluate( this, POST_ITER ); + do_pause(); auto old_active = current_index; if ( !canceled ) @@ -3887,6 +3908,8 @@ void sim_t::create_options() add_option( opt_bool( "merge_enemy_priority_dmg", merge_enemy_priority_dmg ) ); add_option( opt_int( "decorated_tooltips", decorated_tooltips ) ); add_option( opt_uint( "spell_query_wrap", spell_query_wrap ) ); + // Sim Controller Options + add_option( opt_map_list( "profileset_controller.", profileset_controller_options ) ); // Charts add_option( opt_bool( "chart_show_relative_difference", chart_show_relative_difference ) ); add_option( opt_string( "relative_difference_base", relative_difference_base ) ); diff --git a/engine/sim/sim.hpp b/engine/sim/sim.hpp index d449957600d..487f59f6447 100644 --- a/engine/sim/sim.hpp +++ b/engine/sim/sim.hpp @@ -6,11 +6,14 @@ #pragma once #include "config.hpp" + #include "event_manager.hpp" +#include "interfaces/sc_js.hpp" #include "player/gear_stats.hpp" #include "progress_bar.hpp" -#include "sim_ostream.hpp" #include "sim/option.hpp" +#include "profileset_control.hpp" +#include "sim_ostream.hpp" #include "util/concurrency.hpp" #include "util/rng.hpp" #include "util/sample_data.hpp" @@ -19,6 +22,7 @@ #include #include +#include struct actor_target_data_t; struct buff_t; @@ -27,7 +31,7 @@ class dbc_t; class dbc_override_t; struct expr_t; namespace highchart { - struct chart_t; +struct chart_t; } struct iteration_data_entry_t; struct option_t; @@ -46,7 +50,7 @@ class report_configuration_t; } namespace profileset{ - class profilesets_t; +class profilesets_t; } struct sim_progress_t @@ -54,7 +58,7 @@ struct sim_progress_t int current_iterations; int total_iterations; double pct() const - { return std::min( 1.0, current_iterations / static_cast(total_iterations) ); } + { return std::min( 1.0, current_iterations / static_cast( total_iterations ) ); } }; /// Simulation engine @@ -83,7 +87,7 @@ struct sim_t : private sc_thread_t bool fixed_time; bool save_profiles; bool save_profile_with_actions; // When saving full profiles, include actions or not - bool save_full_profile; // save the full profile instead of only active save_e flags + bool save_full_profile; // save the full profile instead of only active save_e flags bool default_actions; // Iteration Controls @@ -97,9 +101,9 @@ struct sim_t : private sc_thread_t int analyze_error_interval, analyze_number; sim_control_t* control; - sim_t* parent; - player_t* target; - player_t* heal_target; + sim_t* parent; + player_t* target; + player_t* heal_target; vector_with_callback target_list; vector_with_callback target_non_sleeping_list; vector_with_callback player_list; @@ -107,56 +111,55 @@ struct sim_t : private sc_thread_t vector_with_callback player_non_sleeping_list; vector_with_callback healing_no_pet_list; vector_with_callback healing_pet_list; - player_t* active_player; - size_t current_index; // Current active player - int num_players; - int num_enemies; - int num_tanks; - int enemy_targets; - int healing; // Creates healing targets. Useful for ferals, I guess. + player_t* active_player; + size_t current_index; // Current active player + int num_players; + int num_enemies; + int num_tanks; + int enemy_targets; + int healing; // Creates healing targets. Useful for ferals, I guess. int global_spawn_index; - int max_player_level; + int max_player_level; rng::truncated_gauss_t queue_lag, gcd_lag, channel_lag; - timespan_t queue_gcd_reduction; - timespan_t default_cooldown_tolerance; - bool strict_gcd_queue; - double confidence, confidence_estimator; + timespan_t queue_gcd_reduction; + timespan_t default_cooldown_tolerance; + bool strict_gcd_queue; + double confidence, confidence_estimator; // Latency rng::truncated_gauss_t world_lag; - double travel_variance, default_skill; - timespan_t reaction_time, regen_periodicity; - timespan_t ignite_sampling_delta; - int optimize_expressions; - int optimize_expressions_rounds; - int current_slot; - int optimal_raid, log, debug_each; + double travel_variance, default_skill; + timespan_t reaction_time, regen_periodicity; + timespan_t ignite_sampling_delta; + int optimize_expressions; + int optimize_expressions_rounds; + int current_slot; + int optimal_raid, log, debug_each; std::vector debug_seed; - stat_e normalized_stat; + stat_e normalized_stat; std::string current_name, default_region_str, default_server_str, save_prefix_str, save_suffix_str; - bool save_talent_str; - auto_dispose< std::vector > actor_list; + bool save_talent_str; + auto_dispose> actor_list; std::string main_target_str; - int stat_cache; - int max_aoe_enemies; - bool requires_regen_event; - bool single_actor_batch; - bool allow_experimental_specializations; - bool enable_all_talents; - bool enable_all_sets; - bool enable_all_item_effects; - int progressbar_type; - int armory_retries; + int stat_cache; + int max_aoe_enemies; + bool requires_regen_event; + bool single_actor_batch; + bool allow_experimental_specializations; + bool enable_all_talents; + bool enable_all_sets; + bool enable_all_item_effects; + int progressbar_type; + int armory_retries; std::unordered_map item_slot_overrides; // Target options - double enemy_death_pct; - int rel_target_level, target_level; + double enemy_death_pct; + int rel_target_level, target_level; std::string target_race; - int target_adds; + int target_adds; std::string sim_progress_base_str, sim_progress_phase_str; - int desired_targets; // desired number of targets - int desired_tank_targets; // desired number of tank target dummy npcs - + int desired_targets; // desired number of targets + int desired_tank_targets; // desired number of tank target dummy npcs // Data access std::unique_ptr dbc; @@ -166,21 +169,21 @@ struct sim_t : private sc_thread_t gear_stats_t enchant; int timewalk; - int scale_to_itemlevel; //itemlevel to scale to. if -1, we don't scale down - bool dungeon_route_smart_targeting; // sets whether the list of mobs will be sorted by their hp - bool challenge_mode; // if active, players will get scaled down to 620 and set bonuses are deactivated - bool scale_itemlevel_down_only; // Items below the value of scale_to_itemlevel will not be scaled up. - bool disable_set_bonuses; // Disables all set bonuses. + int scale_to_itemlevel; // itemlevel to scale to. if -1, we don't scale down + bool dungeon_route_smart_targeting; // sets whether the list of mobs will be sorted by their hp + bool challenge_mode; // if active, players will get scaled down to 620 and set bonuses are deactivated + bool scale_itemlevel_down_only; // Items below the value of scale_to_itemlevel will not be scaled up. + bool disable_set_bonuses; // Disables all set bonuses. bool enable_taunts; - bool use_item_verification; // Disable use-item action verification in the simulator - std::string disable_2_set; // Disables all 2 set bonuses for the tier that this is set as - std::string disable_4_set; // Disables all 4 set bonuses for the tier that this is set as - std::string enable_2_set;// Enables all 2 set bonuses for the tier that this is set as - std::string enable_4_set; // Enables all 4 set bonuses for the tier that this is set as - const spell_data_t* pvp_rules; // Hidden aura that contains the PvP crit damage reduction - bool pvp_mode; // Enables PvP mode - reduces crit damage, adjusts PvP gear iLvl - bool auto_attacks_always_land; /// Allow Auto Attacks (white attacks) to always hit the enemy - bool log_spell_id; // Add spell data ids to log/debug output where available. (actions, buffs) + bool use_item_verification; // Disable use-item action verification in the simulator + std::string disable_2_set; // Disables all 2 set bonuses for the tier that this is set as + std::string disable_4_set; // Disables all 4 set bonuses for the tier that this is set as + std::string enable_2_set; // Enables all 2 set bonuses for the tier that this is set as + std::string enable_4_set; // Enables all 4 set bonuses for the tier that this is set as + const spell_data_t* pvp_rules; // Hidden aura that contains the PvP crit damage reduction + bool pvp_mode; // Enables PvP mode - reduces crit damage, adjusts PvP gear iLvl + bool auto_attacks_always_land; /// Allow Auto Attacks (white attacks) to always hit the enemy + bool log_spell_id; // Add spell data ids to log/debug output where available. (actions, buffs) // Actor tracking int active_enemies; @@ -221,13 +224,13 @@ struct sim_t : private sc_thread_t int bleeding; // Misc stuff needs resolving - int bloodlust; + int bloodlust; std::vector target_health; } overrides; struct auras_t { - buff_t* fallback; // generic global fallback buff + buff_t* fallback; // generic global fallback buff buff_t* arcane_intellect; buff_t* battle_shout; buff_t* mark_of_the_wild; @@ -239,81 +242,81 @@ struct sim_t : private sc_thread_t struct legion_opt_t { // Legion - int infernal_cinders_users = 1; - int engine_of_eradication_orbs = 4; - int void_stalkers_contract_targets = -1; - double specter_of_betrayal_overlap = 1.0; + int infernal_cinders_users = 1; + int engine_of_eradication_orbs = 4; + int void_stalkers_contract_targets = -1; + double specter_of_betrayal_overlap = 1.0; std::vector cradle_of_anguish_resets; } legion_opts; struct bfa_opt_t { /// Chance to spawn the rare droplet - double secrets_of_the_deep_chance = 0.1; // TODO: Guessed, needs validation + double secrets_of_the_deep_chance = 0.1; // TODO: Guessed, needs validation /// Chance that the player collects the droplet, defaults to always - double secrets_of_the_deep_collect_chance = 1.0; + double secrets_of_the_deep_collect_chance = 1.0; /// Gutripper base RPPM when target is above 30% - double gutripper_default_rppm = 2.0; + double gutripper_default_rppm = 2.0; /// Chance to pick up visage spawned by Seductive Power - double seductive_power_pickup_chance = 1.0; + double seductive_power_pickup_chance = 1.0; /// Treacherous Covenant update period. - timespan_t covenant_period = 1.0_s; + timespan_t covenant_period = 1.0_s; /// Chance to gain the buff on each Treacherous Covenant update. - double covenant_chance = 1.0; + double covenant_chance = 1.0; /// Chance to gain a stack of Incandescent Sliver each time it ticks. - double incandescent_sliver_chance = 1.0; + double incandescent_sliver_chance = 1.0; /// Fight or Flight proc attempt period - timespan_t fight_or_flight_period = 1.0_s; + timespan_t fight_or_flight_period = 1.0_s; /// Chance to gain the buff on each Fight or Flight attempt - double fight_or_flight_chance = 0.0; + double fight_or_flight_chance = 0.0; /// Chance of being silenced by Harbinger's Inscrutable Will projectile - double harbingers_inscrutable_will_silence_chance = 0.0; + double harbingers_inscrutable_will_silence_chance = 0.0; /// Chance avoiding Harbinger's Inscrutable Will projectile by moving - double harbingers_inscrutable_will_move_chance = 1.0; + double harbingers_inscrutable_will_move_chance = 1.0; /// Chance player is above 60% HP for Leggings of the Aberrant Tidesage damage proc - double aberrant_tidesage_damage_chance = 1.0; + double aberrant_tidesage_damage_chance = 1.0; /// Chance player is above 90% HP for Fa'thuul's Floodguards damage proc - double fathuuls_floodguards_damage_chance = 1.0; + double fathuuls_floodguards_damage_chance = 1.0; /// Chance player is above 90% HP for Grips of Forgotten Sanity damage proc - double grips_of_forsaken_sanity_damage_chance = 1.0; + double grips_of_forsaken_sanity_damage_chance = 1.0; /// Chance player takes damage and loses Untouchable from Stormglide Steps - double stormglide_steps_take_damage_chance = 0.0; + double stormglide_steps_take_damage_chance = 0.0; /// Duration of the Lurker's Insidious Gift buff, the player can cancel it early to avoid unnecessary damage. 0 = full duration - timespan_t lurkers_insidious_gift_duration = 0_ms; + timespan_t lurkers_insidious_gift_duration = 0_ms; /// Expected duration (in seconds) of shield from Abyssal Speaker's Gauntlets. 0 = full duration - timespan_t abyssal_speakers_gauntlets_shield_duration = 0_ms; + timespan_t abyssal_speakers_gauntlets_shield_duration = 0_ms; /// Expected duration of the absorb provided by Trident of Deep Ocean. 0 = full duration - timespan_t trident_of_deep_ocean_duration = 0_ms; + timespan_t trident_of_deep_ocean_duration = 0_ms; /// Chance that the player has a higher health percentage than the target for Legplates of Unbound Anguish proc - double legplates_of_unbound_anguish_chance = 1.0; + double legplates_of_unbound_anguish_chance = 1.0; /// Period to check for if an ally dies with Loyal to the End - timespan_t loyal_to_the_end_ally_death_timer = 60_s; + timespan_t loyal_to_the_end_ally_death_timer = 60_s; /// Chance on every check to see if an ally dies with Loyal to the End - double loyal_to_the_end_ally_death_chance = 0.0; + double loyal_to_the_end_ally_death_chance = 0.0; /// Number of allies with the Loyal to the End azerite trait, default = 4 (max) - int loyal_to_the_end_allies = 0; + int loyal_to_the_end_allies = 0; /// Number of allies also using the Worldvein Resonance minor - int worldvein_allies = 0; + int worldvein_allies = 0; /// Chance to proc Reality Shift (normally triggers on moving specific distance) - double ripple_in_space_proc_chance = 0.0; + double ripple_in_space_proc_chance = 0.0; /// Chance to be in range to hit with Blood of the Enemy major power (12 yd PBAoE) - double blood_of_the_enemy_in_range = 1.0; + double blood_of_the_enemy_in_range = 1.0; /// Period to check for if Undulating Tides gets locked out - timespan_t undulating_tides_lockout_timer = 60_s; + timespan_t undulating_tides_lockout_timer = 60_s; /// Chance on every check to see if Undulating Tides gets locked out - double undulating_tides_lockout_chance = 0.0; + double undulating_tides_lockout_chance = 0.0; /// Base RPPM for Leviathan's Lure - double leviathans_lure_base_rppm = 0.75; + double leviathans_lure_base_rppm = 0.75; /// Chance to catch returning wave of Aquipotent Nautilus - double aquipotent_nautilus_catch_chance = 1.0; + double aquipotent_nautilus_catch_chance = 1.0; /// Chance of having to interrupt casting by moving to void tear from Za'qul's Portal Key - double zaquls_portal_key_move_chance = 0.0; + double zaquls_portal_key_move_chance = 0.0; /// Unleash stacked potency from Anu-Azshara, Staff of the Eternal after X seconds - timespan_t anuazshara_unleash_time = 0_ms; + timespan_t anuazshara_unleash_time = 0_ms; /// Storm of the Eternal haste and crit stat split ratio. - double storm_of_the_eternal_ratio = 0.05; + double storm_of_the_eternal_ratio = 0.05; /// How long before combat to start channeling Azshara's Font of Power - timespan_t font_of_power_precombat_channel = 0_ms; + timespan_t font_of_power_precombat_channel = 0_ms; /// Average duration of buff in percentage double voidtwisted_titanshard_percent_duration = 0.5; /// Period between checking if surging vitality can proc @@ -327,33 +330,33 @@ struct sim_t : private sc_thread_t /// Percentage of Whispered Truths reductions to be applied to offensive spells. double whispered_truths_offensive_chance = 0.75; /// Initial stacks for Seductive Power buff - int initial_seductive_power_stacks = 0; + int initial_seductive_power_stacks = 0; /// Number of allies affected by Jes' Howler buff - unsigned jes_howler_allies = 4; + unsigned jes_howler_allies = 4; /// Initial stacks for Archive of the Titans - int initial_archive_of_the_titans_stacks = 0; + int initial_archive_of_the_titans_stacks = 0; /// Hps done while using the Azerite Trait Arcane Heart - unsigned arcane_heart_hps = 0; + unsigned arcane_heart_hps = 0; /// Prepull spell cast count to assume. - int subroutine_recalibration_precombat_stacks = 0; + int subroutine_recalibration_precombat_stacks = 0; /// Additional spell cast count to assume each buff cycle. - int subroutine_recalibration_dummy_casts = 0; + int subroutine_recalibration_dummy_casts = 0; /// Number of Reorigination array stats on the actors in the sim - int reorigination_array_stacks = 0; + int reorigination_array_stacks = 0; /// Allow Reorigination Array to ignore scale factor stat changes (default false) - bool reorigination_array_ignore_scale_factors = false; + bool reorigination_array_ignore_scale_factors = false; /// Randomize Variable Intensity Gigavolt Oscillating Reactor start-of-combat oscillation - bool randomize_oscillation = true; + bool randomize_oscillation = true; /// Automatically use Oscillating Overload on max stack, true = yes if no use_item, 0 = no - bool auto_oscillating_overload = true; + bool auto_oscillating_overload = true; /// Is the actor in Zuldazar? Relevant for one of the set bonuses. - bool zuldazar = false; + bool zuldazar = false; /// Whether the player is in Ny'alotha or not. bool nyalotha = true; /// Whether the player is in Nazjatar/Eternal Palace for various effects - bool nazjatar = true; + bool nazjatar = true; /// Whether the Shiver Venom Crossbow/Lance should assume the target has the Shiver Venom debuff - bool shiver_venom = false; + bool shiver_venom = false; } bfa_opts; struct shadowlands_opt_t @@ -417,7 +420,7 @@ struct sim_t : private sc_thread_t /// Sets the default delay that the player waits before facing their Doubt. /// This is disabled if the APL creates the "newfound_resolve" action. timespan_t newfound_resolve_default_delay = 4_s; - double newfound_resolve_delay_relstddev = 0.2; + double newfound_resolve_delay_relstddev = 0.2; /// Seconds between damage/healing triggers for the Pustule Eruption soulbind, has a minimum 1s ICD timespan_t pustule_eruption_interval = 1_s; /// Chance that the player will pickup Shredded Soul orb left by Ebonsoul Vise @@ -447,8 +450,8 @@ struct sim_t : private sc_thread_t bool disable_iqd_execute = false; // Better Together Override // Defaults active - bool better_together_ally = true; - bool enable_rune_words = false; + bool better_together_ally = true; + bool enable_rune_words = false; bool enable_domination_gems = false; // fleshcraft cancel delay from the_first_sigil timespan_t the_first_sigil_fleshcraft_cancel_time = 50_ms; @@ -558,7 +561,7 @@ struct sim_t : private sc_thread_t chrono::wall_clock::duration elapsed_time; std::vector work_per_thread; size_t work_done; - double iteration_dmg, priority_iteration_dmg, iteration_heal, iteration_absorb; + double iteration_dmg, priority_iteration_dmg, iteration_heal, iteration_absorb; simple_sample_data_t total_dmg, raid_hps, total_heal, total_absorb, raid_aps; extended_sample_data_t raid_dps, simulation_length; chrono::wall_clock::duration merge_time, init_time, analyze_time; @@ -566,11 +569,11 @@ struct sim_t : private sc_thread_t // replayability std::vector iteration_data, low_iteration_data, high_iteration_data; // Report percent (how many% of lowest/highest iterations reported, default 2.5%) - double report_iteration_data; + double report_iteration_data; // Minimum number of low/high iterations reported (default 5 of each) - int min_report_iteration_data; - int report_progress; - int bloodlust_percent; + int min_report_iteration_data; + int report_progress; + int bloodlust_percent; timespan_t bloodlust_time; std::string reference_player_str; std::vector players_by_dps; @@ -583,7 +586,7 @@ struct sim_t : private sc_thread_t std::vector players_by_variance; std::vector targets_by_name; std::vector id_dictionary; - std::map > divisor_timeline_cache; + std::map> divisor_timeline_cache; std::vector json_reports; std::string output_file_str, html_file_str, json_file_str; std::string reforge_plot_output_file_str; @@ -622,10 +625,18 @@ struct sim_t : private sc_thread_t double scaling_normalized; bool merge_enemy_priority_dmg; + // sim control + std::vector> profileset_controller; + // deque used as profileset_controller_data_wrapper_t is nocopy, thus std::vector + // is incompatible + std::deque profileset_controller_data; + opts::map_list_t profileset_controller_options; + +public: // Multi-Threading mutex_t merge_mutex; int threads; - std::vector children; // Manual delete! + std::vector children; // Manual delete! int thread_index; computer_process::priority_e process_priority; std::shared_ptr work_queue; @@ -642,7 +653,7 @@ struct sim_t : private sc_thread_t std::string spell_query_xml_output_file_str; unsigned spell_query_wrap; - std::unique_ptr pause_mutex; // External pause mutex, instantiated an external entity (in our case the GUI). + std::unique_ptr pause_mutex; // External pause mutex, instantiated an external entity (in our case the GUI). bool paused; // Highcharts stuff @@ -653,7 +664,7 @@ struct sim_t : private sc_thread_t // A map of highcharts data, added as a json object into the HTML report. JQuery installs handlers // to correct elements (toggled elements in the HTML report) based on the data. - std::map > chart_data; + std::map> chart_data; bool chart_show_relative_difference; // Use the max metric actor as the relative difference base instead of the min @@ -664,7 +675,7 @@ struct sim_t : private sc_thread_t // List of callbacks to call when an actor_target_data_t object is created. Currently used to // initialize the generic targetdata debuffs/dots we have. - std::vector > target_data_initializer; + std::vector> target_data_initializer; bool display_hotfixes, disable_hotfixes; bool display_bonus_ids; @@ -686,47 +697,47 @@ struct sim_t : private sc_thread_t ~sim_t() override; void run() override; - int main( const std::vector& args ); - double iteration_time_adjust(); - double expected_max_time() const; - bool is_canceled() const; - void cancel_iteration(); - void cancel(); - void interrupt(); - void add_relative( sim_t* cousin ); - void remove_relative( sim_t* cousin ); + int main( const std::vector& args ); + double iteration_time_adjust(); + double expected_max_time() const; + bool is_canceled() const; + void cancel_iteration(); + void cancel(); + void interrupt(); + void add_relative( sim_t* cousin ); + void remove_relative( sim_t* cousin ); sim_progress_t progress( std::string* detailed = nullptr, int index = -1 ); - double progress( std::string& phase, std::string* detailed = nullptr, int index = -1 ); - void detailed_progress( std::string*, int current_iterations, int total_iterations ); - void datacollection_begin(); - void datacollection_end(); - void reset(); - void check_actors(); - void init_fight_style(); - void init_parties(); - void init_actors(); - void init_actor( player_t* ); - void init_actor_pets(); - void init(); - void analyze(); - void merge( sim_t& other_sim ); - void merge(); - bool iterate(); - void partition(); - bool execute(); - void analyze_error(); - void analyze_iteration_data(); - void print_options(); - void add_option( std::unique_ptr opt ); - void create_options(); - bool parse_option( const std::string& name, const std::string& value ); - void setup( sim_control_t* ); - bool time_to_think( timespan_t proc_time ); + double progress( std::string& phase, std::string* detailed = nullptr, int index = -1 ); + void detailed_progress( std::string*, int current_iterations, int total_iterations ); + void datacollection_begin(); + void datacollection_end(); + void reset(); + void check_actors(); + void init_fight_style(); + void init_parties(); + void init_actors(); + void init_actor( player_t* ); + void init_actor_pets(); + void init(); + void analyze(); + void merge( sim_t& other_sim ); + void merge(); + bool iterate(); + void partition(); + bool execute(); + void analyze_error(); + void analyze_iteration_data(); + void print_options(); + void add_option( std::unique_ptr opt ); + void create_options(); + bool parse_option( const std::string& name, const std::string& value ); + void setup( sim_control_t* ); + bool time_to_think( timespan_t proc_time ); player_t* find_player( util::string_view name ) const; player_t* find_player( int index ) const; cooldown_t* get_cooldown( util::string_view name ); - void use_optimal_buffs_and_debuffs( int value ); - std::unique_ptr create_expression( util::string_view name ); + void use_optimal_buffs_and_debuffs( int value ); + std::unique_ptr create_expression( util::string_view name ); /** * Create error with printf formatting. @@ -737,7 +748,7 @@ struct sim_t : private sc_thread_t if ( thread_index != 0 ) return; - set_error( level, fmt::sprintf( format, std::forward(args)... ) ); + set_error( level, fmt::sprintf( format, std::forward( args )... ) ); } template @@ -746,7 +757,7 @@ struct sim_t : private sc_thread_t if ( thread_index != 0 ) return; - set_error( error_level_e::TRIVIAL, fmt::sprintf( format, std::forward(args)... ) ); + set_error( error_level_e::TRIVIAL, fmt::sprintf( format, std::forward( args )... ) ); } /** @@ -781,14 +792,14 @@ struct sim_t : private sc_thread_t void activate_actors(); void heartbeat_event_callback(); - std::vector> heartbeat_event_callback_function; - void register_heartbeat_event_callback( std::function fn ); + std::vector> heartbeat_event_callback_function; + void register_heartbeat_event_callback( std::function fn ); timespan_t current_time() const { return event_mgr.current_time; } static double distribution_mean_error( const sim_t& s, const extended_sample_data_t& sd ) { return s.confidence_estimator * sd.mean_std_dev; } - void register_target_data_initializer(std::function cb) + void register_target_data_initializer( std::function cb ) { target_data_initializer.push_back( cb ); } const rng::rng_t& rng() const { return _rng; } @@ -816,9 +827,9 @@ struct sim_t : private sc_thread_t * Print using fmt libraries python-like formatting syntax. */ template - void print_debug( fmt::format_string format, Args&& ... args ) + void print_debug( fmt::format_string format, Args&&... args ) { - if ( ! debug ) + if ( !debug ) return; out_debug.vprint( format, fmt::make_format_args( args... ) ); @@ -831,9 +842,9 @@ struct sim_t : private sc_thread_t * Print using fmt libraries python-like formatting syntax. */ template - void print_log( fmt::format_string format, Args&& ... args ) + void print_log( fmt::format_string format, Args&&... args ) { - if ( ! log ) + if ( !log ) return; out_log.vprint( format, fmt::make_format_args( args... ) ); @@ -849,3 +860,20 @@ struct sim_t : private sc_thread_t void disable_debug_seed(); bool requires_cleanup() const; }; + +template +data_wrapper_t profileset_controller_t::get_data() +{ + auto& pcd = parent->profileset_controller_data; + assert( pcd.size() > id ); + auto& data = pcd[ id ]; + return { *data.data.get(), data.mutex }; +} + +template +void profileset_controller_t::set_data( T&& data ) +{ + auto& pcd = parent->profileset_controller_data; + assert( pcd.size() > id ); + pcd[ id ].data = std::make_unique( data ); +} diff --git a/source_files/QT_engine.pri b/source_files/QT_engine.pri index ec8f083c5c1..89ff6a2cf88 100644 --- a/source_files/QT_engine.pri +++ b/source_files/QT_engine.pri @@ -165,6 +165,7 @@ HEADERS += engine/sim/plot.hpp HEADERS += engine/sim/proc.hpp HEADERS += engine/sim/proc_rng.hpp HEADERS += engine/sim/profileset.hpp +HEADERS += engine/sim/profileset_control.hpp HEADERS += engine/sim/progress_bar.hpp HEADERS += engine/sim/raid_event.hpp HEADERS += engine/sim/reforge_plot.hpp @@ -361,6 +362,7 @@ SOURCES += engine/sim/plot.cpp SOURCES += engine/sim/proc.cpp SOURCES += engine/sim/proc_rng.cpp SOURCES += engine/sim/profileset.cpp +SOURCES += engine/sim/profileset_control.cpp SOURCES += engine/sim/progress_bar.cpp SOURCES += engine/sim/raid_event.cpp SOURCES += engine/sim/reforge_plot.cpp diff --git a/source_files/VS_engine.props b/source_files/VS_engine.props index ad67be4033c..7d1be47fc9a 100644 --- a/source_files/VS_engine.props +++ b/source_files/VS_engine.props @@ -169,6 +169,7 @@ To change the list of source files run synchronize.py + @@ -364,6 +365,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..446e2b032b1 100644 --- a/source_files/cmake_engine.txt +++ b/source_files/cmake_engine.txt @@ -163,6 +163,7 @@ sim/plot.hpp sim/proc.hpp sim/proc_rng.hpp sim/profileset.hpp +sim/profileset_control.hpp sim/progress_bar.hpp sim/raid_event.hpp sim/reforge_plot.hpp @@ -358,6 +359,7 @@ sim/plot.cpp sim/proc.cpp sim/proc_rng.cpp sim/profileset.cpp +sim/profileset_control.cpp sim/progress_bar.cpp sim/raid_event.cpp sim/reforge_plot.cpp diff --git a/source_files/engine_make b/source_files/engine_make index 862193bab57..1047377ba7f 100644 --- a/source_files/engine_make +++ b/source_files/engine_make @@ -163,6 +163,7 @@ SRC += \ sim$(PATHSEP)proc.cpp \ sim$(PATHSEP)proc_rng.cpp \ sim$(PATHSEP)profileset.cpp \ + sim$(PATHSEP)profileset_control.cpp \ sim$(PATHSEP)progress_bar.cpp \ sim$(PATHSEP)raid_event.cpp \ sim$(PATHSEP)reforge_plot.cpp \