Skip to content

Commit c5dfb95

Browse files
authored
Merge pull request #470 from OpenVicProject/load-mod-descriptors
Load all mod descriptors and check dependencies
2 parents 3628619 + 00fa204 commit c5dfb95

File tree

8 files changed

+211
-50
lines changed

8 files changed

+211
-50
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: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
#include "GameManager.hpp"
22

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

511
GameManager::GameManager(
@@ -8,25 +14,124 @@ GameManager::GameManager(
814
new_gamestate_updated_callback ? std::move(new_gamestate_updated_callback) : []() {}
915
}, definitions_loaded { false }, mod_descriptors_loaded { false } {}
1016

11-
bool GameManager::load_mod_descriptors(std::span<const memory::string> descriptors) {
17+
bool GameManager::load_mod_descriptors() {
1218
if (mod_descriptors_loaded) {
1319
Logger::error("Cannot load mod descriptors - already loaded!");
1420
return false;
1521
}
1622

17-
if (!dataloader.load_mod_descriptors(descriptors, mod_manager)) {
23+
if (!dataloader.load_mod_descriptors(mod_manager)) {
1824
Logger::error("Failed to load mod descriptors!");
1925
return false;
2026
}
2127
return true;
2228
}
2329

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

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

src/openvic-simulation/GameManager.hpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
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"
88
#include "openvic-simulation/dataloader/ModManager.hpp"
99
#include "openvic-simulation/dataloader/Dataloader.hpp"
1010
#include "openvic-simulation/misc/GameRulesManager.hpp"
1111
#include "openvic-simulation/gen/commit_info.gen.hpp"
12+
#include "openvic-simulation/utility/ForwardableSpan.hpp"
1213

1314
namespace OpenVic {
1415
struct GameManager {
@@ -23,6 +24,8 @@ namespace OpenVic {
2324
bool PROPERTY_CUSTOM_PREFIX(definitions_loaded, are);
2425
bool PROPERTY_CUSTOM_PREFIX(mod_descriptors_loaded, are);
2526

27+
bool _get_mod_dependencies(Mod const* mod, memory::vector<Mod const*>& load_list);
28+
2629
public:
2730
GameManager(
2831
InstanceManager::gamestate_updated_func_t new_gamestate_updated_callback
@@ -36,9 +39,19 @@ namespace OpenVic {
3639
return instance_manager ? &*instance_manager : nullptr;
3740
}
3841

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

41-
bool load_mod_descriptors(std::span<const memory::string> descriptors);
50+
bool load_mods(
51+
Dataloader::path_vector_t& roots,
52+
Dataloader::path_vector_t& replace_paths,
53+
utility::forwardable_span<const memory::string> requested_mods
54+
);
4255

4356
bool load_definitions(Dataloader::localisation_callback_t localisation_callback);
4457

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: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@
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;
1112

12-
Mod::Mod(std::string_view new_identifier, std::string_view new_path, std::optional<std::string_view> new_user_dir, memory::vector<memory::string> new_replace_paths, memory::vector<memory::string> new_dependencies)
13-
: HasIdentifier { new_identifier }, dataloader_root_path { new_path }, user_dir { new_user_dir }, replace_paths { new_replace_paths }, dependencies { new_dependencies } {}
13+
Mod::Mod(
14+
std::string_view new_identifier,
15+
std::string_view new_path,
16+
std::optional<std::string_view> new_user_dir,
17+
memory::vector<memory::string> new_replace_paths,
18+
memory::vector<memory::string> new_dependencies
19+
)
20+
: HasIdentifier { new_identifier },
21+
dataloader_root_path { new_path },
22+
user_dir { new_user_dir },
23+
replace_paths { new_replace_paths },
24+
dependencies { new_dependencies } {}
1425

1526
ModManager::ModManager() {}
1627

@@ -21,27 +32,42 @@ bool ModManager::load_mod_file(ast::NodeCPtr root) {
2132
memory::vector<memory::string> replace_paths;
2233
memory::vector<memory::string> dependencies;
2334

24-
bool ret = NodeTools::expect_dictionary_keys(
35+
bool ret = NodeTools::expect_dictionary_keys_and_default_map(
36+
map_key_value_ignore_invalid_callback<template_key_map_t<StringMapCaseSensitive>>,
2537
"name", ONE_EXACTLY, expect_string(assign_variable_callback(identifier)),
2638
"path", ONE_EXACTLY, expect_string(assign_variable_callback(path)),
2739
"user_dir", ZERO_OR_ONE, expect_string(assign_variable_callback_opt(user_dir)),
2840
"replace_path", ZERO_OR_MORE, expect_string(vector_callback_string(replace_paths)),
2941
"dependencies", ZERO_OR_ONE, expect_list_reserve_length(dependencies, expect_string(vector_callback_string(dependencies)))
3042
)(root);
3143

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-
}
44+
if (!ret) {
45+
//NodeTools already logs and an invalid (unloaded) mod won't stop the game.
46+
return true;
3847
}
3948

40-
if (ret) {
41-
ret &= mods.emplace_item(
42-
identifier, identifier, path, user_dir, std::move(replace_paths), std::move(dependencies)
43-
);
49+
Logger::info("Loaded mod descriptor for \"", identifier, "\"");
50+
mods.emplace_item(
51+
identifier,
52+
identifier, path, user_dir, std::move(replace_paths), std::move(dependencies)
53+
);
54+
return true;
55+
}
56+
57+
void ModManager::set_loaded_mods(memory::vector<Mod const*>&& new_loaded_mods) {
58+
OV_ERR_FAIL_COND_MSG(mods_loaded, "set_loaded_mods called twice");
59+
60+
loaded_mods = std::move(new_loaded_mods);
61+
mods_loaded = true;
62+
for (Mod const* mod : loaded_mods) {
63+
Logger::info("Loading mod \"", mod->get_identifier(), "\" at path ", mod->get_dataloader_root_path());
4464
}
65+
}
66+
67+
memory::vector<Mod const*> const& ModManager::get_loaded_mods() const {
68+
return loaded_mods;
69+
}
4570

46-
return ret;
47-
}
71+
size_t ModManager::get_loaded_mod_count() const {
72+
return loaded_mods.size();
73+
}

0 commit comments

Comments
 (0)