Skip to content

Commit 39ef1a5

Browse files
committed
Load all mod descriptors and check dependencies
1 parent 3a61a0a commit 39ef1a5

File tree

8 files changed

+182
-49
lines changed

8 files changed

+182
-49
lines changed

src/headless/main.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,19 +133,16 @@ static bool run_headless(fs::path const& root, memory::vector<memory::string>& m
133133

134134
Logger::info("Commit hash: ", GameManager::get_commit_hash());
135135

136+
Logger::info("===== Setting base path... =====");
137+
ret &= game_manager.set_base_path(roots);
138+
136139
Logger::info("===== Loading mod descriptors... =====");
137-
ret &= game_manager.load_mod_descriptors(mods);
138-
for (auto const& mod : game_manager.get_mod_manager().get_mods()) {
139-
roots.emplace_back(root / mod.get_dataloader_root_path());
140-
for (std::string_view path : mod.get_replace_paths()) {
141-
if (std::find(replace_paths.begin(), replace_paths.end(), path) == replace_paths.end()) {
142-
replace_paths.emplace_back(path);
143-
}
144-
}
145-
}
140+
ret &= game_manager.load_mod_descriptors();
141+
142+
Logger::info("===== Loading mods... =====");
143+
ret &= game_manager.load_mods(roots, replace_paths, mods);
146144

147145
Logger::info("===== Loading definitions... =====");
148-
ret &= game_manager.set_roots(roots, replace_paths);
149146
ret &= game_manager.load_definitions(
150147
[](std::string_view key, Dataloader::locale_t locale, std::string_view localisation) -> bool {
151148
return true;

src/openvic-simulation/GameManager.cpp

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
#include "GameManager.hpp"
22

3+
#include <cstddef>
4+
#include <string_view>
5+
#include <vector>
6+
7+
#include "openvic-simulation/dataloader/Dataloader.hpp"
8+
#include "openvic-simulation/utility/Logger.hpp"
9+
310
using namespace OpenVic;
411

512
GameManager::GameManager(
@@ -8,25 +15,120 @@ GameManager::GameManager(
815
new_gamestate_updated_callback ? std::move(new_gamestate_updated_callback) : []() {}
916
}, definitions_loaded { false }, mod_descriptors_loaded { false } {}
1017

11-
bool GameManager::load_mod_descriptors(std::span<const memory::string> descriptors) {
18+
bool GameManager::load_mod_descriptors() {
1219
if (mod_descriptors_loaded) {
1320
Logger::error("Cannot load mod descriptors - already loaded!");
1421
return false;
1522
}
1623

17-
if (!dataloader.load_mod_descriptors(descriptors, mod_manager)) {
24+
if (!dataloader.load_mod_descriptors(mod_manager)) {
1825
Logger::error("Failed to load mod descriptors!");
1926
return false;
2027
}
2128
return true;
2229
}
2330

24-
bool GameManager::set_roots(Dataloader::path_span_t roots, Dataloader::path_span_t replace_paths) {
25-
if (!dataloader.set_roots(roots, replace_paths)) {
31+
bool GameManager::_get_mod_dependencies(Mod const* mod, std::vector<Mod const*>& dep_list) {
32+
static constexpr size_t MAX_RECURSE = 16;
33+
size_t current_recurse = 0;
34+
35+
static auto dep_cycle = [this, &current_recurse](auto self, Mod const* mod, std::vector<Mod const*>& dep_list) -> bool {
36+
bool ret = true;
37+
for (std::string_view dep_identifier : mod->get_dependencies()) {
38+
if (!mod_manager.has_mod_identifier(dep_identifier)) {
39+
Logger::error("Mod \"", mod->get_identifier(), "\" has unmet dependency \"", dep_identifier, "\" and cannot be loaded!");
40+
return false;
41+
}
42+
Mod const* dep = mod_manager.get_mod_by_identifier(dep_identifier);
43+
/* The poor man's cycle checking (cycles should be very rare and hard to accomplish with vic2 modding, this is a failsafe) */
44+
if (current_recurse == MAX_RECURSE) {
45+
Logger::error("Mod \"", mod->get_identifier(), "\" has cyclical or broken dependency chain and cannot be loaded!");
46+
return false;
47+
} else {
48+
current_recurse++;
49+
ret &= self(self, dep, dep_list); /* recursively search for mod dependencies */
50+
}
51+
if (std::find(dep_list.begin(), dep_list.end(), dep) == dep_list.end()) {
52+
dep_list.emplace_back(dep);
53+
}
54+
}
55+
return ret;
56+
};
57+
return dep_cycle(dep_cycle, mod, dep_list);
58+
}
59+
60+
bool GameManager::load_mods(Dataloader::path_vector_t& roots, Dataloader::path_vector_t& replace_paths, std::span<const memory::string> requested_mods) {
61+
if (requested_mods.empty()) {
62+
return true;
63+
}
64+
65+
bool ret = true;
66+
67+
std::vector<Mod const*> load_list;
68+
69+
/* Check loaded mod descriptors for requested mods, using either full name or user directory name
70+
* (Historical Project Mod 0.4.6 or HPM both valid, for example), and load them plus their dependencies.
71+
*/
72+
for (std::string_view requested_mod : requested_mods) {
73+
auto it = std::find_if(
74+
mod_manager.get_mods().begin(),
75+
mod_manager.get_mods().end(),
76+
[&requested_mod](Mod const& mod) -> bool {
77+
return mod.get_identifier() == requested_mod || mod.get_user_dir() == requested_mod;
78+
}
79+
);
80+
81+
if (it == mod_manager.get_mods().end()) {
82+
Logger::error("Requested mod \"", requested_mod, "\" does not exist!");
83+
ret = false;
84+
continue;
85+
}
86+
87+
Mod const* mod_ptr = &*it;
88+
std::vector<Mod const*> dependencies;
89+
if(!_get_mod_dependencies(mod_ptr, dependencies)) {
90+
ret = false;
91+
continue;
92+
}
93+
94+
/* Add mod plus dependencies to load_list in proper order. */
95+
load_list.reserve(1 + dependencies.size());
96+
for (Mod const* dep : dependencies) {
97+
if (ret && std::find(load_list.begin(), load_list.end(), dep) == load_list.end()) {
98+
load_list.emplace_back(dep);
99+
}
100+
}
101+
if (ret && std::find(load_list.begin(), load_list.end(), mod_ptr) == load_list.end()) {
102+
load_list.emplace_back(mod_ptr);
103+
}
104+
}
105+
106+
/* Actually registers all roots and replace paths to be loaded by the game. */
107+
for (Mod const* mod : load_list) {
108+
roots.emplace_back(roots[0] / mod->get_dataloader_root_path());
109+
for (std::string_view path : mod->get_replace_paths()) {
110+
if (std::find(replace_paths.begin(), replace_paths.end(), path) == replace_paths.end()) {
111+
replace_paths.emplace_back(path);
112+
}
113+
}
114+
}
115+
116+
/* Load only vanilla and push an error if mod loading failed. */
117+
if (ret) {
118+
mod_manager.set_loaded_mods(std::move(load_list));
119+
} else {
120+
mod_manager.set_loaded_mods({});
121+
replace_paths.clear();
122+
roots.erase(roots.begin()+1, roots.end());
123+
Logger::error("Mod loading failed, loading base only!");
124+
}
125+
126+
if (!dataloader.set_roots(roots, replace_paths, false)) {
26127
Logger::error("Failed to set dataloader roots!");
27-
return false;
128+
ret = false;
28129
}
29-
return true;
130+
131+
return ret;
30132
}
31133

32134
bool GameManager::load_definitions(Dataloader::localisation_callback_t localisation_callback) {

src/openvic-simulation/GameManager.hpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#pragma once
22

33
#include <optional>
4-
#include <span>
4+
#include <string_view>
55

66
#include "openvic-simulation/DefinitionManager.hpp"
77
#include "openvic-simulation/InstanceManager.hpp"
@@ -23,6 +23,8 @@ namespace OpenVic {
2323
bool PROPERTY_CUSTOM_PREFIX(definitions_loaded, are);
2424
bool PROPERTY_CUSTOM_PREFIX(mod_descriptors_loaded, are);
2525

26+
bool _get_mod_dependencies(Mod const* mod, std::vector<Mod const*>& load_list);
27+
2628
public:
2729
GameManager(
2830
InstanceManager::gamestate_updated_func_t new_gamestate_updated_callback
@@ -36,9 +38,15 @@ namespace OpenVic {
3638
return instance_manager ? &*instance_manager : nullptr;
3739
}
3840

39-
bool set_roots(Dataloader::path_span_t roots, Dataloader::path_span_t replace_paths);
41+
inline bool set_base_path(Dataloader::path_span_t base_path) {
42+
OV_ERR_FAIL_COND_V_MSG(base_path.size() > 1, "more than one dataloader base path provided", false);
43+
OV_ERR_FAIL_COND_V_MSG(!dataloader.set_roots(base_path, {}), "failed to set dataloader base path", false);
44+
return true;
45+
};
46+
47+
bool load_mod_descriptors();
4048

41-
bool load_mod_descriptors(std::span<const memory::string> descriptors);
49+
bool load_mods(Dataloader::path_vector_t& roots, Dataloader::path_vector_t& replace_paths, std::span<const memory::string> requested_mods);
4250

4351
bool load_definitions(Dataloader::localisation_callback_t localisation_callback);
4452

src/openvic-simulation/dataloader/Dataloader.cpp

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "Dataloader.hpp"
22

3+
#include <string_view>
34
#include <system_error>
45

56
#include <openvic-dataloader/csv/Parser.hpp>
@@ -36,9 +37,11 @@ static fs::path ensure_forward_slash_path(std::string_view path) {
3637
#endif
3738
}
3839

39-
bool Dataloader::set_roots(path_span_t new_roots, path_span_t new_replace_paths) {
40+
bool Dataloader::set_roots(path_span_t new_roots, path_span_t new_replace_paths, bool warn_on_override) {
4041
if (!roots.empty()) {
41-
Logger::warning("Overriding existing dataloader roots!");
42+
if (warn_on_override) {
43+
Logger::warning("Overriding existing dataloader roots!");
44+
}
4245
roots.clear();
4346
replace_paths.clear();
4447
}
@@ -307,19 +310,23 @@ void Dataloader::free_cache() {
307310
cached_parsers.clear();
308311
}
309312

310-
bool Dataloader::load_mod_descriptors(std::span<const memory::string> descriptors, ModManager& mod_manager) {
311-
bool ret = true;
313+
bool Dataloader::load_mod_descriptors(ModManager& mod_manager) const {
314+
static constexpr std::string_view mod_directory = "mod";
315+
static constexpr std::string_view mod_descriptor_extension = ".mod";
312316

313-
for (std::string_view descriptor_path : descriptors) {
314-
if (!mod_manager.load_mod_file(parse_defines(ensure_forward_slash_path(descriptor_path)).get_file_node())) {
315-
Logger::error("Failed to load ", descriptor_path);
316-
ret = false;
317+
apply_to_files(
318+
lookup_files_in_dir(mod_directory, mod_descriptor_extension),
319+
[&mod_manager](fs::path const& file) -> bool {
320+
if (!mod_manager.load_mod_file(parse_defines(file).get_file_node())) {
321+
Logger::warning("Invalid mod descriptor at path: ", file, " could not be loaded!");
322+
}
323+
return true;
317324
}
318-
}
325+
);
319326

320327
mod_manager.lock_mods();
321328

322-
return ret;
329+
return true;
323330
}
324331

325332
bool Dataloader::_load_interface_files(UIManager& ui_manager) const {

src/openvic-simulation/dataloader/Dataloader.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ namespace OpenVic {
102102
///
103103
/// @param new_roots Dataloader roots in reverse-load order, so base defines first and final loaded mod last
104104
/// @param new_replace_paths All base define paths that should be ignored entirely in favour of mods.
105+
/// @param warn_on_override Whether or not to log a warning if roots are overridden.
105106
/// @return True if successful, false if failed.
106-
bool set_roots(path_span_t new_roots, path_span_t new_replace_paths);
107+
bool set_roots(path_span_t new_roots, path_span_t new_replace_paths, bool warn_on_override = true);
107108

108109
/* REQUIREMENTS:
109110
* DAT-24
@@ -126,8 +127,8 @@ namespace OpenVic {
126127

127128
string_set_t lookup_dirs_in_dir(std::string_view path) const;
128129

129-
/* Load all mod descriptors passed by the user. Importantly, loads dependencies and replace_paths for us to check. */
130-
bool load_mod_descriptors(std::span<const memory::string> descriptors, ModManager& mod_manager);
130+
/* Load all mod descriptors present in the mod/ directory. Importantly, loads dependencies and replace_paths for us to check. */
131+
bool load_mod_descriptors(ModManager& mod_manager) const;
131132

132133
/* Load and parse all of the text defines data, including parsing cached condition and effect scripts after all the
133134
* static data is loaded. Paths to the base and mod defines must have been supplied with set_roots.*/

src/openvic-simulation/dataloader/ModManager.cpp

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "openvic-simulation/dataloader/NodeTools.hpp"
66
#include "openvic-simulation/types/HasIdentifier.hpp"
77
#include "openvic-simulation/types/IdentifierRegistry.hpp"
8+
#include "openvic-simulation/utility/ErrorMacros.hpp"
89

910
using namespace OpenVic;
1011
using namespace OpenVic::NodeTools;
@@ -21,27 +22,38 @@ bool ModManager::load_mod_file(ast::NodeCPtr root) {
2122
memory::vector<memory::string> replace_paths;
2223
memory::vector<memory::string> dependencies;
2324

24-
bool ret = NodeTools::expect_dictionary_keys(
25+
bool ret = NodeTools::expect_dictionary_keys_and_default_map(
26+
map_key_value_ignore_invalid_callback<template_key_map_t<StringMapCaseSensitive>>,
2527
"name", ONE_EXACTLY, expect_string(assign_variable_callback(identifier)),
2628
"path", ONE_EXACTLY, expect_string(assign_variable_callback(path)),
2729
"user_dir", ZERO_OR_ONE, expect_string(assign_variable_callback_opt(user_dir)),
2830
"replace_path", ZERO_OR_MORE, expect_string(vector_callback_string(replace_paths)),
2931
"dependencies", ZERO_OR_ONE, expect_list_reserve_length(dependencies, expect_string(vector_callback_string(dependencies)))
3032
)(root);
3133

32-
memory::vector<std::string_view> previous_mods = mods.get_item_identifiers();
33-
for (std::string_view dependency : dependencies) {
34-
if (std::find(previous_mods.begin(), previous_mods.end(), dependency) == previous_mods.end()) {
35-
ret = false;
36-
Logger::error("Mod ", identifier, " has unmet dependency ", dependency);
37-
}
38-
}
34+
OV_ERR_FAIL_COND_V(!ret, true);
35+
36+
Logger::info("Loaded mod descriptor for \"", identifier, "\"");
37+
mods.add_item(
38+
{ identifier, path, user_dir, std::move(replace_paths), std::move(dependencies) }
39+
);
40+
return true;
41+
}
3942

40-
if (ret) {
41-
ret &= mods.add_item(
42-
{ identifier, path, user_dir, std::move(replace_paths), std::move(dependencies) }
43-
);
43+
void ModManager::set_loaded_mods(std::vector<Mod const*>&& new_loaded_mods) {
44+
OV_ERR_FAIL_COND_MSG(mods_loaded, "set_loaded_mods called twice");
45+
46+
loaded_mods = std::move(new_loaded_mods);
47+
mods_loaded = true;
48+
for (Mod const* mod : loaded_mods) {
49+
Logger::info("Loading mod \"", mod->get_identifier(), "\" at path ", mod->get_dataloader_root_path());
4450
}
51+
}
52+
53+
std::vector<Mod const*> const& ModManager::get_loaded_mods() const {
54+
return loaded_mods;
55+
}
4556

46-
return ret;
47-
}
57+
size_t ModManager::get_loaded_mod_count() const {
58+
return loaded_mods.size();
59+
}

src/openvic-simulation/dataloader/ModManager.hpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <cstddef>
34
#include <string_view>
45
#include <vector>
56

@@ -25,13 +26,18 @@ namespace OpenVic {
2526
};
2627

2728
struct ModManager {
28-
29+
2930
private:
3031
IdentifierRegistry<Mod> IDENTIFIER_REGISTRY(mod);
32+
std::vector<Mod const*> loaded_mods;
33+
bool mods_loaded = false;
3134

3235
public:
3336
ModManager();
3437

3538
bool load_mod_file(ast::NodeCPtr root);
39+
void set_loaded_mods(std::vector<Mod const*>&& new_loaded_mods);
40+
std::vector<Mod const*> const& get_loaded_mods() const;
41+
size_t get_loaded_mod_count() const;
3642
};
37-
} // namespace OpenVic
43+
} // namespace OpenVic

tests/benchmarks/src/dataloading/Dataloading.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ TEST_CASE("Dataloading benchmark", "[benchmarks][benchmark-dataloading]") {
1313
ankerl::nanobench::Bench().epochs(10).run("Dataloading", [&] {
1414
OpenVic::GameManager game_manager { []() {} };
1515

16-
game_manager.set_roots(roots, {});
16+
game_manager.set_base_path(roots);
1717
game_manager.load_definitions(
1818
[](std::string_view key, Dataloader::locale_t locale, std::string_view localisation) -> bool {
1919
return true;

0 commit comments

Comments
 (0)