rewrite ServerState dependency management

This commit is contained in:
Martin Michelsen
2023-12-31 21:25:39 -08:00
parent a24d0ad703
commit f479f586cb
7 changed files with 420 additions and 349 deletions
+2 -2
View File
@@ -123,10 +123,10 @@ set(SOURCES
src/ServerState.cc src/ServerState.cc
src/Shell.cc src/Shell.cc
src/StaticGameData.cc src/StaticGameData.cc
src/StepGraph.cc
src/TeamIndex.cc src/TeamIndex.cc
src/Text.cc src/Text.cc
src/TextArchive.cc src/TextIndex.cc
src/UnicodeTextSet.cc
src/Version.cc src/Version.cc
src/WordSelectTable.cc src/WordSelectTable.cc
) )
+37 -48
View File
@@ -1493,24 +1493,20 @@ Action a_show_ep3_cards(
+[](Arguments& args) { +[](Arguments& args) {
bool one_line = args.get<bool>("one-line"); bool one_line = args.get<bool>("one-line");
Episode3::CardIndex card_index( ServerState s;
"system/ep3/card-definitions.mnr", s.load_objects("ep3_data");
"system/ep3/card-definitions.mnrd",
"system/ep3/card-text.mnr", unique_ptr<BinaryTextSet> text_english;
"system/ep3/card-text.mnrd",
"system/ep3/card-dice-text.mnr",
"system/ep3/card-dice-text.mnrd");
unique_ptr<TextArchive> text_english;
try { try {
JSON json = JSON::parse(load_file("system/ep3/text-english.json")); JSON json = JSON::parse(load_file("system/ep3/text-english.json"));
text_english = make_unique<TextArchive>(json); text_english = make_unique<BinaryTextSet>(json);
} catch (const exception& e) { } catch (const exception& e) {
} }
auto card_ids = card_index.all_ids(); auto card_ids = s.ep3_card_index->all_ids();
log_info("%zu card definitions", card_ids.size()); log_info("%zu card definitions", card_ids.size());
for (uint32_t card_id : card_ids) { for (uint32_t card_id : card_ids) {
auto entry = card_index.definition_for_id(card_id); auto entry = s.ep3_card_index->definition_for_id(card_id);
string s = entry->def.str(one_line, text_english.get()); string s = entry->def.str(one_line, text_english.get());
if (one_line) { if (one_line) {
fprintf(stdout, "%s\n", s.c_str()); fprintf(stdout, "%s\n", s.c_str());
@@ -1544,18 +1540,14 @@ Action a_generate_ep3_cards_html(
+[](Arguments& args) { +[](Arguments& args) {
size_t num_threads = args.get<size_t>("threads", 0); size_t num_threads = args.get<size_t>("threads", 0);
Episode3::CardIndex card_index( ServerState s;
"system/ep3/card-definitions.mnr", s.load_objects("ep3_data");
"system/ep3/card-definitions.mnrd", s.load_objects("text_index");
"system/ep3/card-text.mnr",
"system/ep3/card-text.mnrd", shared_ptr<const TextSet> text_english;
"system/ep3/card-dice-text.mnr",
"system/ep3/card-dice-text.mnrd");
unique_ptr<TextArchive> text_english;
try { try {
JSON json = JSON::parse(load_file("system/ep3/text-english.json")); text_english = s.text_index->get(Version::GC_EP3, 1);
text_english = make_unique<TextArchive>(json); } catch (const out_of_range&) {
} catch (const exception& e) {
} }
struct CardInfo { struct CardInfo {
@@ -1572,11 +1564,11 @@ Action a_generate_ep3_cards_html(
} }
}; };
vector<CardInfo> infos; vector<CardInfo> infos;
for (uint32_t card_id : card_index.all_ids()) { for (uint32_t card_id : s.ep3_card_index->all_ids()) {
if (infos.size() <= card_id) { if (infos.size() <= card_id) {
infos.resize(card_id + 1); infos.resize(card_id + 1);
} }
infos[card_id].ce = card_index.definition_for_id(card_id); infos[card_id].ce = s.ep3_card_index->definition_for_id(card_id);
} }
for (const auto& filename : list_directory_sorted("system/ep3/cardtex")) { for (const auto& filename : list_directory_sorted("system/ep3/cardtex")) {
if ((filename[0] == 'C' || filename[0] == 'M' || filename[0] == 'L') && (filename[1] == '_')) { if ((filename[0] == 'C' || filename[0] == 'M' || filename[0] == 'L') && (filename[1] == '_')) {
@@ -1684,20 +1676,21 @@ Action a_show_ep3_maps(
human-readable format.\n", human-readable format.\n",
+[](Arguments&) { +[](Arguments&) {
config_log.info("Collecting Episode 3 data"); config_log.info("Collecting Episode 3 data");
Episode3::MapIndex map_index("system/ep3/maps");
Episode3::CardIndex card_index("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd");
auto map_ids = map_index.all_numbers(); ServerState s;
s.load_objects("ep3_data");
auto map_ids = s.ep3_map_index->all_numbers();
log_info("%zu maps", map_ids.size()); log_info("%zu maps", map_ids.size());
for (uint32_t map_id : map_ids) { for (uint32_t map_id : map_ids) {
auto map = map_index.for_number(map_id); auto map = s.ep3_map_index->for_number(map_id);
const auto& vms = map->all_versions(); const auto& vms = map->all_versions();
for (size_t language = 0; language < vms.size(); language++) { for (size_t language = 0; language < vms.size(); language++) {
if (!vms[language]) { if (!vms[language]) {
continue; continue;
} }
string s = vms[language]->map->str(&card_index, language); string map_s = vms[language]->map->str(s.ep3_card_index.get(), language);
fprintf(stdout, "(%c) %s\n", char_for_language_code(language), s.c_str()); fprintf(stdout, "(%c) %s\n", char_for_language_code(language), map_s.c_str());
} }
} }
}); });
@@ -1708,26 +1701,21 @@ Action a_show_battle_params(
Print the Blue Burst battle parameters from the system/blueburst directory\n\ Print the Blue Burst battle parameters from the system/blueburst directory\n\
in a human-readable format.\n", in a human-readable format.\n",
+[](Arguments&) { +[](Arguments&) {
BattleParamsIndex index( ServerState s;
make_shared<string>(load_file("system/blueburst/BattleParamEntry_on.dat")), s.load_objects("battle_params");
make_shared<string>(load_file("system/blueburst/BattleParamEntry_lab_on.dat")),
make_shared<string>(load_file("system/blueburst/BattleParamEntry_ep4_on.dat")),
make_shared<string>(load_file("system/blueburst/BattleParamEntry.dat")),
make_shared<string>(load_file("system/blueburst/BattleParamEntry_lab.dat")),
make_shared<string>(load_file("system/blueburst/BattleParamEntry_ep4.dat")));
fprintf(stdout, "Episode 1 multi\n"); fprintf(stdout, "Episode 1 multi\n");
index.get_table(false, Episode::EP1).print(stdout); s.battle_params->get_table(false, Episode::EP1).print(stdout);
fprintf(stdout, "Episode 1 solo\n"); fprintf(stdout, "Episode 1 solo\n");
index.get_table(true, Episode::EP1).print(stdout); s.battle_params->get_table(true, Episode::EP1).print(stdout);
fprintf(stdout, "Episode 2 multi\n"); fprintf(stdout, "Episode 2 multi\n");
index.get_table(false, Episode::EP2).print(stdout); s.battle_params->get_table(false, Episode::EP2).print(stdout);
fprintf(stdout, "Episode 2 solo\n"); fprintf(stdout, "Episode 2 solo\n");
index.get_table(true, Episode::EP2).print(stdout); s.battle_params->get_table(true, Episode::EP2).print(stdout);
fprintf(stdout, "Episode 4 multi\n"); fprintf(stdout, "Episode 4 multi\n");
index.get_table(false, Episode::EP4).print(stdout); s.battle_params->get_table(false, Episode::EP4).print(stdout);
fprintf(stdout, "Episode 4 solo\n"); fprintf(stdout, "Episode 4 solo\n");
index.get_table(true, Episode::EP4).print(stdout); s.battle_params->get_table(true, Episode::EP4).print(stdout);
}); });
Action a_parse_object_graph( Action a_parse_object_graph(
@@ -1848,12 +1836,13 @@ Action a_diff_dol_files(
Action a_replay_ep3_battle_commands( Action a_replay_ep3_battle_commands(
"replay-ep3-battle-commands", nullptr, +[](Arguments& args) { "replay-ep3-battle-commands", nullptr, +[](Arguments& args) {
auto card_index = make_shared<Episode3::CardIndex>("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd"); ServerState s;
auto map_index = make_shared<Episode3::MapIndex>("system/ep3/maps"); s.load_objects("ep3_data");
auto random_crypt = make_shared<PSOV2Encryption>(args.get<uint32_t>("seed", 0, Arguments::IntFormat::HEX)); auto random_crypt = make_shared<PSOV2Encryption>(args.get<uint32_t>("seed", 0, Arguments::IntFormat::HEX));
Episode3::Server::Options options = { Episode3::Server::Options options = {
.card_index = card_index, .card_index = s.ep3_card_index,
.map_index = map_index, .map_index = s.ep3_map_index,
.behavior_flags = 0x0092, .behavior_flags = 0x0092,
.random_crypt = random_crypt, .random_crypt = random_crypt,
.tournament = nullptr, .tournament = nullptr,
@@ -1897,7 +1886,7 @@ Action a_run_server_replay_log(
shared_ptr<struct event_base> base(event_base_new(), event_base_free); shared_ptr<struct event_base> base(event_base_new(), event_base_free);
auto state = make_shared<ServerState>(base, config_filename, is_replay); auto state = make_shared<ServerState>(base, config_filename, is_replay);
state->init(); state->load_objects("all");
shared_ptr<DNSServer> dns_server; shared_ptr<DNSServer> dns_server;
if (state->dns_server_port && !is_replay) { if (state->dns_server_port && !is_replay) {
+23 -43
View File
@@ -120,17 +120,24 @@ General commands:\n\
\n\ \n\
Server commands:\n\ Server commands:\n\
reload ITEM [ITEM...]\n\ reload ITEM [ITEM...]\n\
Reload various parts of the server configuration. ITEMs can be:\n\ Reload various parts of the server configuration. When you reload any item,\n\
licenses - reload the license index file\n\ any other item that depends on it will be reloaded as well. The items are:\n\
patches - reindex the PC and BB patch directories\n\ all - reindex/reload everything\n\
battle-params - reload the enemy stats files\n\ battle-params - reload the BB enemy stats files\n\
level-table - reload the level-up tables\n\ bb-private-keys - reload BB private keys\n\
item-tables - reload the item generation tables\n\
ep3 - reload Episode 3 card definitions and maps (not download quests)\n\
quests - reindex all quests (including Episode 3 download quests)\n\
functions - recompile all client-side functions\n\
dol-files - reindex all DOL files\n\
config - reload most fields from config.json\n\ config - reload most fields from config.json\n\
dol-files - reindex all DOL files\n\
drop-tables - reload drop tables\n\
ep3-data - reload Episode 3 cards and maps (not download quests)\n\
functions - recompile all client-side patches and functions\n\
item-definitions - reload item definitions files\n\
level-table - reload the level-up tables\n\
licenses - reindex user licenses\n\
patch-indexes - reindex the PC and BB patch directories\n\
quest-index - reindex all quests (including Episode3 download quests)\n\
teams - reindex all BB teams\n\
text-index - reload in-game text\n\
word-select-table - regenerate the Word Select translation table\n\
Reloading will not affect items that are in use; for example, if an Episode\n\ Reloading will not affect items that are in use; for example, if an Episode\n\
3 battle is in progress, it will continue to use the previous map and card\n\ 3 battle is in progress, it will continue to use the previous map and card\n\
definitions. Similarly, BB clients are not forced to disconnect or reload\n\ definitions. Similarly, BB clients are not forced to disconnect or reload\n\
@@ -285,41 +292,14 @@ Proxy session commands:\n\
if (types.empty()) { if (types.empty()) {
throw invalid_argument("no data type given"); throw invalid_argument("no data type given");
} }
for (const string& type : types) { for (auto& type : types) {
if (type == "licenses") { for (char& ch : type) {
this->state->load_licenses(); if (ch == '-') {
} else if (type == "teams") { ch = '_';
this->state->load_teams(); }
} else if (type == "patches") {
this->state->load_patch_indexes();
} else if (type == "battle-params") {
this->state->load_battle_params();
} else if (type == "level-table") {
this->state->load_level_table();
} else if (type == "item-tables") {
this->state->load_item_name_index();
this->state->load_item_tables();
} else if (type == "word-select") {
this->state->load_word_select_table();
} else if (type == "ep3") {
this->state->load_ep3_data();
} else if (type == "quests") {
this->state->load_quest_index();
} else if (type == "functions") {
auto config_json = this->state->load_config();
this->state->compile_functions();
} else if (type == "dol-files") {
auto config_json = this->state->load_config();
this->state->load_dol_files();
} else if (type == "config") {
auto config_json = this->state->load_config();
this->state->parse_config(config_json, true);
this->state->resolve_ep3_card_names();
this->state->load_teams();
} else {
throw invalid_argument("incorrect data type");
} }
} }
this->state->load_objects(types);
} else if (command_name == "add-license") { } else if (command_name == "add-license") {
auto l = this->state->license_index->create_license(); auto l = this->state->license_index->create_license();
+194 -198
View File
@@ -25,137 +25,31 @@ ServerState::QuestF960Result::QuestF960Result(const JSON& json, std::shared_ptr<
this->probability_upgrade = json.get_int("ProbabilityUpgrade", 0); this->probability_upgrade = json.get_int("ProbabilityUpgrade", 0);
for (size_t day = 0; day < 7; day++) { for (size_t day = 0; day < 7; day++) {
for (const auto& item_it : json.get_list(day_names[day])) { for (const auto& item_it : json.get_list(day_names[day])) {
this->results[day].emplace_back(name_index->parse_item_description(Version::BB_V4, item_it->as_string())); this->results[day].emplace_back(name_index->parse_item_description(item_it->as_string()));
} }
} }
} }
ServerState::ServerState() : creation_time(now()) {
this->create_load_step_graph();
}
ServerState::ServerState(shared_ptr<struct event_base> base, const string& config_filename, bool is_replay) ServerState::ServerState(shared_ptr<struct event_base> base, const string& config_filename, bool is_replay)
: creation_time(now()), : creation_time(now()),
base(base), base(base),
config_filename(config_filename), config_filename(config_filename),
is_replay(is_replay), is_replay(is_replay),
dns_server_port(0), player_files_manager(this->base ? make_shared<PlayerFilesManager>(base) : nullptr),
ip_stack_debug(false), destroy_lobbies_event(this->base ? event_new(base.get(), -1, EV_TIMEOUT, &ServerState::dispatch_destroy_lobbies, this) : nullptr, event_free) {
allow_unregistered_users(false), this->create_load_step_graph();
allow_pc_nte(false), }
use_temp_licenses_for_prototypes(true),
allow_dc_pc_games(false),
allow_gc_xb_games(true),
allowed_drop_modes_v1_v2_normal(0x1F),
allowed_drop_modes_v1_v2_battle(0x07),
allowed_drop_modes_v1_v2_challenge(0x07),
allowed_drop_modes_v3_normal(0x1F),
allowed_drop_modes_v3_battle(0x07),
allowed_drop_modes_v3_challenge(0x07),
allowed_drop_modes_v4_normal(0x1D), // CLIENT not allowed
allowed_drop_modes_v4_battle(0x05),
allowed_drop_modes_v4_challenge(0x05),
default_drop_mode_v1_v2_normal(Lobby::DropMode::CLIENT),
default_drop_mode_v1_v2_battle(Lobby::DropMode::CLIENT),
default_drop_mode_v1_v2_challenge(Lobby::DropMode::CLIENT),
default_drop_mode_v3_normal(Lobby::DropMode::CLIENT),
default_drop_mode_v3_battle(Lobby::DropMode::CLIENT),
default_drop_mode_v3_challenge(Lobby::DropMode::CLIENT),
default_drop_mode_v4_normal(Lobby::DropMode::SERVER_SHARED),
default_drop_mode_v4_battle(Lobby::DropMode::SERVER_SHARED),
default_drop_mode_v4_challenge(Lobby::DropMode::SERVER_SHARED),
persistent_game_idle_timeout_usecs(0),
ep3_send_function_call_enabled(false),
catch_handler_exceptions(true),
ep3_infinite_meseta(false),
ep3_defeat_player_meseta_rewards({400, 500, 600, 700, 800}),
ep3_defeat_com_meseta_rewards({100, 200, 300, 400, 500}),
ep3_final_round_meseta_bonus(300),
ep3_jukebox_is_free(false),
ep3_behavior_flags(0),
hide_download_commands(true),
run_shell_behavior(RunShellBehavior::DEFAULT),
cheat_mode_behavior(BehaviorSwitch::OFF_BY_DEFAULT),
bb_global_exp_multiplier(1),
ep3_card_auction_points(0),
ep3_card_auction_min_size(0),
ep3_card_auction_max_size(0),
player_files_manager(make_shared<PlayerFilesManager>(base)),
destroy_lobbies_event(event_new(base.get(), -1, EV_TIMEOUT, &ServerState::dispatch_destroy_lobbies, this), event_free),
next_lobby_id(1),
pre_lobby_event(0),
ep3_menu_song(-1),
local_address(0),
external_address(0),
proxy_allow_save_files(true),
proxy_enable_login_options(false) {}
void ServerState::init() { void ServerState::load_objects(const std::string& what) {
vector<shared_ptr<Lobby>> non_v1_only_lobbies; this->load_step_graph.run(what);
vector<shared_ptr<Lobby>> ep3_only_lobbies; }
for (size_t x = 0; x < 20; x++) { void ServerState::load_objects(const std::vector<std::string>& what) {
auto lobby_name = string_printf("LOBBY%zu", x + 1); this->load_step_graph.run(what);
bool allow_v1 = (x <= 9);
bool allow_non_ep3 = (x <= 14);
shared_ptr<Lobby> l = this->create_lobby(false);
l->set_flag(Lobby::Flag::PUBLIC);
l->set_flag(Lobby::Flag::DEFAULT);
l->set_flag(Lobby::Flag::PERSISTENT);
if (allow_non_ep3) {
if (allow_v1) {
l->allow_version(Version::DC_NTE);
l->allow_version(Version::DC_V1_11_2000_PROTOTYPE);
l->allow_version(Version::DC_V1);
}
l->allow_version(Version::DC_V2);
l->allow_version(Version::PC_NTE);
l->allow_version(Version::PC_V2);
l->allow_version(Version::GC_NTE);
l->allow_version(Version::GC_V3);
l->allow_version(Version::XB_V3);
l->allow_version(Version::BB_V4);
}
l->allow_version(Version::GC_EP3_NTE);
l->allow_version(Version::GC_EP3);
l->block = x + 1;
l->name = lobby_name;
l->max_clients = 12;
if (!allow_non_ep3) {
l->episode = Episode::EP3;
}
if (allow_non_ep3) {
this->public_lobby_search_order.emplace_back(l);
} else {
ep3_only_lobbies.emplace_back(l);
}
}
// Annoyingly, the CARD lobbies should be searched first, but are sent at the
// end of the lobby list command, so we have to change the search order
// manually here.
this->public_lobby_search_order.insert(
this->public_lobby_search_order.begin(),
ep3_only_lobbies.begin(),
ep3_only_lobbies.end());
// Load all the necessary data
auto config = this->load_config();
this->collect_network_addresses();
this->load_item_name_index();
this->parse_config(config, false);
this->load_bb_private_keys();
this->load_licenses();
this->load_teams();
this->load_patch_indexes();
this->load_battle_params();
this->load_level_table();
this->load_item_tables();
this->load_word_select_table();
this->load_ep3_data();
this->resolve_ep3_card_names();
this->load_quest_index();
this->compile_functions();
this->load_dol_files();
} }
void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) { void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
@@ -578,20 +472,6 @@ shared_ptr<const string> ServerState::load_bb_file(
} }
} }
void ServerState::collect_network_addresses() {
config_log.info("Reading network addresses");
this->all_addresses = get_local_addresses();
for (const auto& it : this->all_addresses) {
string addr_str = string_for_address(it.second);
config_log.info("Found interface: %s = %s", it.first.c_str(), addr_str.c_str());
}
}
JSON ServerState::load_config() const {
config_log.info("Loading configuration");
return JSON::parse(load_file(this->config_filename));
}
static vector<PortConfiguration> parse_port_configuration(const JSON& json) { static vector<PortConfiguration> parse_port_configuration(const JSON& json) {
vector<PortConfiguration> ret; vector<PortConfiguration> ret;
for (const auto& item_json_it : json.as_dict()) { for (const auto& item_json_it : json.as_dict()) {
@@ -605,8 +485,22 @@ static vector<PortConfiguration> parse_port_configuration(const JSON& json) {
return ret; return ret;
} }
void ServerState::parse_config(const JSON& json, bool is_reload) { void ServerState::collect_network_addresses() {
config_log.info("Parsing configuration"); config_log.info("Reading network addresses");
this->all_addresses = get_local_addresses();
for (const auto& it : this->all_addresses) {
string addr_str = string_for_address(it.second);
config_log.info("Found interface: %s = %s", it.first.c_str(), addr_str.c_str());
}
}
void ServerState::load_config() {
if (this->config_filename.empty()) {
throw logic_error("configuration filename is missing");
}
config_log.info("Loading configuration");
auto json = JSON::parse(load_file(this->config_filename));
auto parse_behavior_switch = [&](const string& json_key, BehaviorSwitch default_value) -> ServerState::BehaviorSwitch { auto parse_behavior_switch = [&](const string& json_key, BehaviorSwitch default_value) -> ServerState::BehaviorSwitch {
try { try {
@@ -629,7 +523,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->name = json.at("ServerName").as_string(); this->name = json.at("ServerName").as_string();
if (!is_reload) { if (!this->config_loaded) {
try { try {
this->username = json.at("User").as_string(); this->username = json.at("User").as_string();
if (this->username == "$SUDO_USER") { if (this->username == "$SUDO_USER") {
@@ -772,12 +666,17 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
try { try {
for (const auto& it : json.get_dict("CardAuctionPool")) { for (const auto& it : json.get_dict("CardAuctionPool")) {
uint16_t card_id;
try {
card_id = this->ep3_card_index->definition_for_name_normalized(it.first)->def.card_id;
} catch (const out_of_range&) {
throw runtime_error(string_printf("Ep3 card \"%s\" in auction pool does not exist", it.first.c_str()));
}
this->ep3_card_auction_pool.emplace_back( this->ep3_card_auction_pool.emplace_back(
CardAuctionPoolEntry{ CardAuctionPoolEntry{
.probability = static_cast<uint64_t>(it.second->at(0).as_int()), .probability = static_cast<uint64_t>(it.second->at(0).as_int()),
.card_id = 0, .card_id = card_id,
.min_price = static_cast<uint16_t>(it.second->at(1).as_int()), .min_price = static_cast<uint16_t>(it.second->at(1).as_int())});
.card_name = it.first});
} }
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
@@ -788,11 +687,18 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
if (ep3_trap_cards_json.size() != 5) { if (ep3_trap_cards_json.size() != 5) {
throw runtime_error("Episode3TrapCards must be a list of 5 lists"); throw runtime_error("Episode3TrapCards must be a list of 5 lists");
} }
this->ep3_trap_card_names.clear(); for (size_t trap_type = 0; trap_type < 5; trap_type++) {
for (const auto& trap_type_it : ep3_trap_cards_json) { auto& trap_card_ids = this->ep3_trap_card_ids[trap_type];
auto& names = this->ep3_trap_card_names.emplace_back(); for (const auto& card_it : ep3_trap_cards_json.at(trap_type)->as_list()) {
for (const auto& card_it : trap_type_it->as_list()) { try {
names.emplace_back(card_it->as_string()); const auto& card = this->ep3_card_index->definition_for_name_normalized(card_it->as_string());
if (card->def.type != Episode3::CardType::ASSIST) {
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list is not an assist card", name.c_str()));
}
trap_card_ids.emplace_back(card->def.card_id);
} catch (const out_of_range&) {
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list does not exist", name.c_str()));
}
} }
} }
} }
@@ -800,6 +706,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
} }
if (!this->is_replay) { if (!this->is_replay) {
this->ep3_lobby_banners.clear();
for (const auto& it : json.get("Episode3LobbyBanners", JSON::list()).as_list()) { for (const auto& it : json.get("Episode3LobbyBanners", JSON::list()).as_list()) {
Image img("system/ep3/banners/" + it->at(2).as_string()); Image img("system/ep3/banners/" + it->at(2).as_string());
string gvm = encode_gvm(img, img.get_has_alpha() ? GVRDataFormat::RGB5A3 : GVRDataFormat::RGB565); string gvm = encode_gvm(img, img.get_has_alpha() ? GVRDataFormat::RGB5A3 : GVRDataFormat::RGB565);
@@ -889,13 +796,11 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
set_log_levels_from_json(json.get("LogLevels", JSON::dict())); set_log_levels_from_json(json.get("LogLevels", JSON::dict()));
if (!is_reload) { try {
try { this->run_shell_behavior = json.at("RunInteractiveShell").as_bool()
this->run_shell_behavior = json.at("RunInteractiveShell").as_bool() ? ServerState::RunShellBehavior::ALWAYS
? ServerState::RunShellBehavior::ALWAYS : ServerState::RunShellBehavior::NEVER;
: ServerState::RunShellBehavior::NEVER; } catch (const out_of_range&) {
} catch (const out_of_range&) {
}
} }
this->allow_dc_pc_games = json.get_bool("AllowDCPCGames", this->allow_dc_pc_games); this->allow_dc_pc_games = json.get_bool("AllowDCPCGames", this->allow_dc_pc_games);
@@ -913,13 +818,11 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->ep3_menu_song = json.get_int("Episode3MenuSong", this->ep3_menu_song); this->ep3_menu_song = json.get_int("Episode3MenuSong", this->ep3_menu_song);
if (!is_reload) { try {
try { this->quest_category_index = make_shared<QuestCategoryIndex>(json.at("QuestCategories"));
this->quest_category_index = make_shared<QuestCategoryIndex>(json.at("QuestCategories")); } catch (const exception& e) {
} catch (const exception& e) { throw runtime_error(string_printf(
throw runtime_error(string_printf( "QuestCategories is missing or invalid in config.json (%s) - see config.example.json for an example", e.what()));
"QuestCategories is missing or invalid in config.json (%s) - see config.example.json for an example", e.what()));
}
} }
config_log.info("Creating menus"); config_log.info("Creating menus");
@@ -1071,6 +974,8 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
} }
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
this->config_loaded = true;
} }
void ServerState::load_bb_private_keys() { void ServerState::load_bb_private_keys() {
@@ -1093,7 +998,6 @@ void ServerState::load_licenses() {
void ServerState::load_teams() { void ServerState::load_teams() {
config_log.info("Indexing teams"); config_log.info("Indexing teams");
this->team_index = make_shared<TeamIndex>("system/teams", this->team_reward_defs_json); this->team_index = make_shared<TeamIndex>("system/teams", this->team_reward_defs_json);
this->team_reward_defs_json = nullptr;
} }
void ServerState::load_patch_indexes() { void ServerState::load_patch_indexes() {
@@ -1393,37 +1297,6 @@ void ServerState::load_ep3_data() {
config_log.info("Loaded Episode 3 tournament state"); config_log.info("Loaded Episode 3 tournament state");
} }
void ServerState::resolve_ep3_card_names() {
config_log.info("Resolving Episode 3 card names");
for (auto& e : this->ep3_card_auction_pool) {
try {
const auto& card = this->ep3_card_index->definition_for_name_normalized(e.card_name);
e.card_id = card->def.card_id;
} catch (const out_of_range&) {
throw runtime_error(string_printf("Ep3 card \"%s\" in auction pool does not exist", e.card_name.c_str()));
}
}
for (size_t z = 0; z < this->ep3_trap_card_ids.size(); z++) {
auto& ids = this->ep3_trap_card_ids[z];
ids.clear();
if (z < this->ep3_trap_card_names.size()) {
auto& names = this->ep3_trap_card_names[z];
for (const auto& name : names) {
try {
const auto& card = this->ep3_card_index->definition_for_name_normalized(name);
if (card->def.type != Episode3::CardType::ASSIST) {
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list is not an assist card", name.c_str()));
}
ids.emplace_back(card->def.card_id);
} catch (const out_of_range&) {
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list does not exist", name.c_str()));
}
}
}
}
}
void ServerState::load_quest_index() { void ServerState::load_quest_index() {
config_log.info("Collecting quests"); config_log.info("Collecting quests");
this->default_quest_index = make_shared<QuestIndex>("system/quests", this->quest_category_index, false); this->default_quest_index = make_shared<QuestIndex>("system/quests", this->quest_category_index, false);
@@ -1441,14 +1314,137 @@ void ServerState::load_dol_files() {
this->dol_file_index = make_shared<DOLFileIndex>("system/dol"); this->dol_file_index = make_shared<DOLFileIndex>("system/dol");
} }
shared_ptr<const vector<string>> ServerState::information_contents_for_client(shared_ptr<const Client> c) const { void ServerState::create_default_lobbies() {
return is_v1_or_v2(c->version()) ? this->information_contents_v2 : this->information_contents_v3; if (this->default_lobbies_created) {
return;
}
this->default_lobbies_created = true;
vector<shared_ptr<Lobby>> non_v1_only_lobbies;
vector<shared_ptr<Lobby>> ep3_only_lobbies;
for (size_t x = 0; x < 20; x++) {
auto lobby_name = string_printf("LOBBY%zu", x + 1);
bool allow_v1 = (x <= 9);
bool allow_non_ep3 = (x <= 14);
shared_ptr<Lobby> l = this->create_lobby(false);
l->event = this->pre_lobby_event;
l->set_flag(Lobby::Flag::PUBLIC);
l->set_flag(Lobby::Flag::DEFAULT);
l->set_flag(Lobby::Flag::PERSISTENT);
if (allow_non_ep3) {
if (allow_v1) {
l->allow_version(Version::DC_NTE);
l->allow_version(Version::DC_V1_11_2000_PROTOTYPE);
l->allow_version(Version::DC_V1);
}
l->allow_version(Version::DC_V2);
l->allow_version(Version::PC_NTE);
l->allow_version(Version::PC_V2);
l->allow_version(Version::GC_NTE);
l->allow_version(Version::GC_V3);
l->allow_version(Version::XB_V3);
l->allow_version(Version::BB_V4);
}
l->allow_version(Version::GC_EP3_NTE);
l->allow_version(Version::GC_EP3);
l->block = x + 1;
l->name = lobby_name;
l->max_clients = 12;
if (!allow_non_ep3) {
l->episode = Episode::EP3;
}
if (allow_non_ep3) {
this->public_lobby_search_order.emplace_back(l);
} else {
ep3_only_lobbies.emplace_back(l);
}
}
// Annoyingly, the CARD lobbies should be searched first, but are sent at the
// end of the lobby list command, so we have to change the search order
// manually here.
this->public_lobby_search_order.insert(
this->public_lobby_search_order.begin(),
ep3_only_lobbies.begin(),
ep3_only_lobbies.end());
} }
shared_ptr<const QuestIndex> ServerState::quest_index_for_version(Version version) const { void ServerState::create_load_step_graph() {
return is_ep3(version) ? this->ep3_download_quest_index : this->default_quest_index; this->load_step_graph.add_step("all", {}, nullptr);
}
void ServerState::dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx) { // In: none
reinterpret_cast<ServerState*>(ctx)->lobbies_to_destroy.clear(); // Out: all_addresses
this->load_step_graph.add_step("network_addresses", {"all"}, bind(&ServerState::collect_network_addresses, this));
// In: none
// Out: bb_private_keys
this->load_step_graph.add_step("bb_private_keys", {"all"}, bind(&ServerState::load_bb_private_keys, this));
// In: none
// Out: license_index
this->load_step_graph.add_step("licenses", {"all"}, bind(&ServerState::load_licenses, this));
// In: none
// Out: pc_patch_file_index, bb_patch_file_index, bb_data_gsl
this->load_step_graph.add_step("patch_indexes", {"all"}, bind(&ServerState::load_patch_indexes, this));
// In: none
// Out: ep3_map_index, ep3_card_index, ep3_card_index_trial, ep3_com_deck_index, ep3_tournament_index
this->load_step_graph.add_step("ep3_data", {"all"}, bind(&ServerState::load_ep3_data, this));
// In: none
// Out: function_code_index
this->load_step_graph.add_step("functions", {"all"}, bind(&ServerState::compile_functions, this));
// In: none
// Out: dol_file_index
this->load_step_graph.add_step("dol_files", {"all"}, bind(&ServerState::load_dol_files, this));
// In: none
// Out: lobbies
this->load_step_graph.add_step("lobbies", {"all"}, bind(&ServerState::create_default_lobbies, this));
// In: bb_patch_file_index
// Out: battle_params
this->load_step_graph.add_step("battle_params", {"all", "patch_indexes"}, bind(&ServerState::load_battle_params, this));
// In: bb_patch_file_index
// Out: level_table
this->load_step_graph.add_step("level_table", {"all", "patch_indexes"}, bind(&ServerState::load_level_table, this));
// In: bb_patch_file_index
// Out: text_index
this->load_step_graph.add_step("text_index", {"all", "patch_indexes"}, bind(&ServerState::load_text_index, this));
// In: text_index (optional)
// Out: word_select_table
this->load_step_graph.add_step("word_select_table", {"all"}, bind(&ServerState::load_word_select_table, this));
// In: none
// Out: item_parameter_tables, mag_evolution_table
this->load_step_graph.add_step("item_definitions", {"all"}, bind(&ServerState::load_item_definitions, this));
// In: text_index, item_parameter_tables
// Out: item_name_indexes
this->load_step_graph.add_step("item_name_indexes", {"all", "text_index", "item_definitions"}, bind(&ServerState::load_item_name_indexes, this));
// In: none
// Out: rare_item_sets, common_item_sets, armor_random_set, tool_random_set, weapon_random_sets, tekker_adjustment_set
this->load_step_graph.add_step("drop_tables", {"all", "item_definitions", "item_name_indexes"}, bind(&ServerState::load_drop_tables, this));
// In: all_addresses, ep3_card_index, item_name_indexes
// Out: config, ep3_lobby_banners, quest_category_index, information menus, proxy destinations menus, team_reward_defs_json
this->load_step_graph.add_step("config", {"all", "network_addresses", "ep3_data", "item_name_indexes"}, bind(&ServerState::load_config, this));
// In: team_reward_defs_json
// Out: team_index
this->load_step_graph.add_step("teams", {"all", "config"}, bind(&ServerState::load_teams, this));
// In: quest_category_index
// Out: default_quest_index, ep3_download_quest_index
this->load_step_graph.add_step("quest_index", {"all", "config"}, bind(&ServerState::load_quest_index, this));
} }
+66 -58
View File
@@ -25,6 +25,7 @@
#include "Menu.hh" #include "Menu.hh"
#include "PlayerFilesManager.hh" #include "PlayerFilesManager.hh"
#include "Quest.hh" #include "Quest.hh"
#include "StepGraph.hh"
#include "TeamIndex.hh" #include "TeamIndex.hh"
#include "WordSelectTable.hh" #include "WordSelectTable.hh"
@@ -63,52 +64,56 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
std::string config_filename; std::string config_filename;
bool is_replay; bool is_replay = false;
bool config_loaded = false;
bool default_lobbies_created = false;
StepGraph load_step_graph;
std::string name; std::string name;
std::unordered_map<std::string, std::shared_ptr<PortConfiguration>> name_to_port_config; std::unordered_map<std::string, std::shared_ptr<PortConfiguration>> name_to_port_config;
std::unordered_map<uint16_t, std::shared_ptr<PortConfiguration>> number_to_port_config; std::unordered_map<uint16_t, std::shared_ptr<PortConfiguration>> number_to_port_config;
std::string username; std::string username;
uint16_t dns_server_port; uint16_t dns_server_port = 0;
std::vector<std::string> ip_stack_addresses; std::vector<std::string> ip_stack_addresses;
std::vector<std::string> ppp_stack_addresses; std::vector<std::string> ppp_stack_addresses;
bool ip_stack_debug; bool ip_stack_debug = false;
bool allow_unregistered_users; bool allow_unregistered_users = false;
bool allow_pc_nte; bool allow_pc_nte = false;
bool use_temp_licenses_for_prototypes; bool use_temp_licenses_for_prototypes = true;
bool allow_dc_pc_games; bool allow_dc_pc_games = true;
bool allow_gc_xb_games; bool allow_gc_xb_games = true;
uint8_t allowed_drop_modes_v1_v2_normal; uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
uint8_t allowed_drop_modes_v1_v2_battle; uint8_t allowed_drop_modes_v1_v2_battle = 0x07;
uint8_t allowed_drop_modes_v1_v2_challenge; uint8_t allowed_drop_modes_v1_v2_challenge = 0x07;
uint8_t allowed_drop_modes_v3_normal; uint8_t allowed_drop_modes_v3_normal = 0x1F;
uint8_t allowed_drop_modes_v3_battle; uint8_t allowed_drop_modes_v3_battle = 0x07;
uint8_t allowed_drop_modes_v3_challenge; uint8_t allowed_drop_modes_v3_challenge = 0x07;
uint8_t allowed_drop_modes_v4_normal; uint8_t allowed_drop_modes_v4_normal = 0x1D; // CLIENT not allowed
uint8_t allowed_drop_modes_v4_battle; uint8_t allowed_drop_modes_v4_battle = 0x05;
uint8_t allowed_drop_modes_v4_challenge; uint8_t allowed_drop_modes_v4_challenge = 0x05;
Lobby::DropMode default_drop_mode_v1_v2_normal; Lobby::DropMode default_drop_mode_v1_v2_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_battle; Lobby::DropMode default_drop_mode_v1_v2_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_challenge; Lobby::DropMode default_drop_mode_v1_v2_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_normal; Lobby::DropMode default_drop_mode_v3_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_battle; Lobby::DropMode default_drop_mode_v3_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_challenge; Lobby::DropMode default_drop_mode_v3_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v4_normal; Lobby::DropMode default_drop_mode_v4_normal = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_battle; Lobby::DropMode default_drop_mode_v4_battle = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_challenge; Lobby::DropMode default_drop_mode_v4_challenge = Lobby::DropMode::SERVER_SHARED;
QuestFlagsForDifficulty quest_flag_persist_mask; QuestFlagsForDifficulty quest_flag_persist_mask;
uint64_t persistent_game_idle_timeout_usecs; uint64_t persistent_game_idle_timeout_usecs = 0;
bool ep3_send_function_call_enabled; bool ep3_send_function_call_enabled = false;
bool catch_handler_exceptions; bool catch_handler_exceptions = true;
bool ep3_infinite_meseta; bool ep3_infinite_meseta = false;
std::vector<uint32_t> ep3_defeat_player_meseta_rewards; std::vector<uint32_t> ep3_defeat_player_meseta_rewards = {400, 500, 600, 700, 800};
std::vector<uint32_t> ep3_defeat_com_meseta_rewards; std::vector<uint32_t> ep3_defeat_com_meseta_rewards = {100, 200, 300, 400, 500};
uint32_t ep3_final_round_meseta_bonus; uint32_t ep3_final_round_meseta_bonus = 300;
bool ep3_jukebox_is_free; bool ep3_jukebox_is_free = false;
uint32_t ep3_behavior_flags; uint32_t ep3_behavior_flags = 0;
bool hide_download_commands; bool hide_download_commands = true;
RunShellBehavior run_shell_behavior; RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT;
BehaviorSwitch cheat_mode_behavior; BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys; std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
std::shared_ptr<const FunctionCodeIndex> function_code_index; std::shared_ptr<const FunctionCodeIndex> function_code_index;
std::shared_ptr<const PatchFileIndex> pc_patch_file_index; std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
@@ -161,21 +166,19 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::vector<QuestF960Result> quest_F960_success_results; std::vector<QuestF960Result> quest_F960_success_results;
QuestF960Result quest_F960_failure_results; QuestF960Result quest_F960_failure_results;
std::vector<ItemData> secret_lottery_results; std::vector<ItemData> secret_lottery_results;
uint16_t bb_global_exp_multiplier; uint16_t bb_global_exp_multiplier = 1;
std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index; std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index;
uint16_t ep3_card_auction_points; uint16_t ep3_card_auction_points = 0;
uint16_t ep3_card_auction_min_size; uint16_t ep3_card_auction_min_size = 0;
uint16_t ep3_card_auction_max_size; uint16_t ep3_card_auction_max_size = 0;
struct CardAuctionPoolEntry { struct CardAuctionPoolEntry {
uint64_t probability; uint64_t probability;
uint16_t card_id; uint16_t card_id;
uint16_t min_price; uint16_t min_price;
std::string card_name;
}; };
std::vector<CardAuctionPoolEntry> ep3_card_auction_pool; std::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
std::vector<std::vector<std::string>> ep3_trap_card_names;
std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids; std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids;
struct Ep3LobbyBannerEntry { struct Ep3LobbyBannerEntry {
uint32_t type = 1; uint32_t type = 1;
@@ -212,26 +215,28 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::unordered_set<std::shared_ptr<Lobby>> lobbies_to_destroy; std::unordered_set<std::shared_ptr<Lobby>> lobbies_to_destroy;
std::shared_ptr<struct event> destroy_lobbies_event; std::shared_ptr<struct event> destroy_lobbies_event;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order; std::vector<std::shared_ptr<Lobby>> public_lobby_search_order;
std::atomic<int32_t> next_lobby_id; std::atomic<int32_t> next_lobby_id = 1;
uint8_t pre_lobby_event; uint8_t pre_lobby_event = 0;
int32_t ep3_menu_song; int32_t ep3_menu_song = -1;
std::map<std::string, uint32_t> all_addresses; std::map<std::string, uint32_t> all_addresses;
uint32_t local_address; uint32_t local_address = 0;
uint32_t external_address; uint32_t external_address = 0;
bool proxy_allow_save_files; bool proxy_allow_save_files = true;
bool proxy_enable_login_options; bool proxy_enable_login_options = false;
std::shared_ptr<ProxyServer> proxy_server; std::shared_ptr<ProxyServer> proxy_server;
std::shared_ptr<Server> game_server; std::shared_ptr<Server> game_server;
ServerState();
ServerState(std::shared_ptr<struct event_base> base, const std::string& config_filename, bool is_replay); ServerState(std::shared_ptr<struct event_base> base, const std::string& config_filename, bool is_replay);
ServerState(const ServerState&) = delete; ServerState(const ServerState&) = delete;
ServerState(ServerState&&) = delete; ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete; ServerState& operator=(const ServerState&) = delete;
ServerState& operator=(ServerState&&) = delete; ServerState& operator=(ServerState&&) = delete;
void init(); void load_objects(const std::string& what);
void load_objects(const std::vector<std::string>& what);
void add_client_to_available_lobby(std::shared_ptr<Client> c); void add_client_to_available_lobby(std::shared_ptr<Client> c);
void remove_client_from_lobby(std::shared_ptr<Client> c); void remove_client_from_lobby(std::shared_ptr<Client> c);
@@ -278,21 +283,24 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
const std::string& gsl_filename = "", const std::string& gsl_filename = "",
const std::string& bb_directory_filename = "") const; const std::string& bb_directory_filename = "") const;
JSON load_config() const; void create_load_step_graph();
void create_default_lobbies();
void collect_network_addresses(); void collect_network_addresses();
void parse_config(const JSON& config_json, bool is_reload); void load_config();
void load_bb_private_keys(); void load_bb_private_keys();
void load_licenses(); void load_licenses();
void load_teams(); void load_teams();
void load_patch_indexes(); void load_patch_indexes();
void load_battle_params(); void load_battle_params();
void load_level_table(); void load_level_table();
void load_item_name_index(); void load_text_index();
void load_item_tables(); static std::shared_ptr<ItemNameIndex> create_item_name_index_for_version(
static std::shared_ptr<WordSelectTable> load_word_select_table_from_system(); Version version, std::shared_ptr<const ItemParameterTable> pmt, std::shared_ptr<const TextIndex> text_index);
void load_item_name_indexes();
void load_drop_tables();
void load_item_definitions();
void load_word_select_table(); void load_word_select_table();
void load_ep3_data(); void load_ep3_data();
void resolve_ep3_card_names();
void load_quest_index(); void load_quest_index();
void compile_functions(); void compile_functions();
void load_dol_files(); void load_dol_files();
+71
View File
@@ -0,0 +1,71 @@
#include "StepGraph.hh"
using namespace std;
void StepGraph::add_step(const string& name, const vector<string>& depends_on_names, function<void()>&& execute) {
auto new_step = make_shared<Step>(Step{.execute = std::move(execute)});
this->steps.emplace(name, new_step);
for (const auto& depends_on_name : depends_on_names) {
auto upstream_step = this->steps.at(depends_on_name);
upstream_step->downstream_dependencies.emplace_back(new_step);
}
}
void StepGraph::run(const string& start_step_name) {
vector<string> start_step_names({start_step_name});
this->run(start_step_names);
}
void StepGraph::run(const vector<string>& start_step_names) {
// Collect all steps to run
deque<shared_ptr<Step>> steps_to_visit;
try {
for (const auto& start_step_name : start_step_names) {
steps_to_visit.emplace_back(this->steps.at(start_step_name));
}
} catch (const out_of_range&) {
throw runtime_error("invalid step name");
}
unordered_set<shared_ptr<Step>> steps_to_run;
while (!steps_to_visit.empty()) {
auto step = std::move(steps_to_visit.front());
steps_to_visit.pop_front();
if (steps_to_run.emplace(step).second) {
for (const auto& downstream_step : step->downstream_dependencies) {
steps_to_visit.emplace_back(downstream_step);
}
}
}
// Topological sort: repeatedly take all steps that are not a downstream
// dependency of any other step in the set
vector<shared_ptr<Step>> steps_order;
steps_order.reserve(steps_to_run.size());
while (!steps_to_run.empty()) {
unordered_set<shared_ptr<Step>> candidate_steps = steps_to_run;
for (const auto& step : steps_to_run) {
for (const auto& downstream_step : step->downstream_dependencies) {
candidate_steps.erase(downstream_step);
}
}
if (candidate_steps.empty()) {
throw logic_error("dependency graph contains a cycle");
}
for (const auto& step : candidate_steps) {
steps_to_run.erase(step);
steps_order.emplace_back(step);
}
}
// Run the steps in order
uint64_t run_id = ++this->last_run_id;
for (auto step : steps_order) {
if (step->last_run_id < run_id) {
step->last_run_id = run_id;
if (step->execute) {
step->execute();
}
}
}
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <phosg/Strings.hh>
#include <string>
#include <unordered_map>
#include <vector>
struct StepGraph {
struct Step {
std::vector<std::shared_ptr<Step>> downstream_dependencies;
std::function<void()> execute;
uint64_t last_run_id = 0;
};
std::unordered_map<std::string, std::shared_ptr<Step>> steps;
uint64_t last_run_id = 0;
StepGraph() = default;
void add_step(const std::string& name, const std::vector<std::string>& depends_on_names, std::function<void()>&& execute);
void run(const std::string& start_step);
void run(const std::vector<std::string>& start_steps);
};