Fix dependency resolution on startup & unload failed startup mods

This commit is contained in:
Luke Street
2026-07-08 18:27:52 -06:00
parent 2840843e3c
commit 4de3c20431
+43 -26
View File
@@ -476,6 +476,11 @@ static std::string mod_enabled_cvar_name(std::string_view const id) {
return fmt::format("mod.{}.enabled", escape_mod_id_for_config(id));
}
static bool required_deps_active(const LoadedMod& mod) {
return std::ranges::all_of(mod.dependencies,
[](const ModDependencyEdge& edge) { return !edge.required || edge.mod->active; });
}
// A deferred export that was not published by the end of the provider's initialization can
// never resolve, which is almost certainly a bug in the provider.
static void warn_unpublished_deferred_exports(const LoadedMod& mod) {
@@ -576,6 +581,7 @@ bool ModLoader::activate_mod(LoadedMod& mod) {
if (!mod.servicesRegistered) {
if (!register_static_service_exports(mod)) {
log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to register service exports");
deactivate_mod(mod);
return false;
}
mod.servicesRegistered = true;
@@ -583,6 +589,7 @@ bool ModLoader::activate_mod(LoadedMod& mod) {
if (!resolve_service_imports(mod)) {
log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to resolve service imports");
deactivate_mod(mod);
return false;
}
@@ -608,7 +615,7 @@ bool ModLoader::activate_mod(LoadedMod& mod) {
if (!mod.active) {
// Failed initialization may have left hooks or other service state behind
svc::modules_mod_detached(mod);
deactivate_mod(mod);
return false;
}
@@ -722,30 +729,39 @@ void ModLoader::init() {
init_services();
// Providers must initialize (and publish deferred services) before their importers, so
// imports are resolved per mod, interleaved with initialization, in dependency order.
loader::sort_mods(m_mods);
// Decide the startup lifecycle state before publishing exports. Config-disabled mods and
// mods blocked by required providers keep dependency edges but must not resolve or provide
// services until they can actually initialize.
for (auto& mod : mods()) {
if (!mod.native) {
if (!mod.cvarIsEnabled->getValue()) {
log::write(mod.metadata.id, LOG_LEVEL_INFO, "disabled by config");
mod.active = false;
mod.suspendedByProvider = false;
continue;
}
if (!mod.loadFailed && !required_deps_active(mod)) {
log::write(
mod.metadata.id, LOG_LEVEL_INFO, "suspended: a required provider is disabled");
mod.active = false;
mod.suspendedByProvider = true;
continue;
}
mod.suspendedByProvider = false;
}
for (auto& mod : mods()) {
if (!mod.active || !mod.native) {
continue;
}
if (register_static_service_exports(mod)) {
mod.servicesRegistered = true;
} else {
log::write(mod.metadata.id, LOG_LEVEL_ERROR, "failed to register service exports");
}
}
// Providers must initialize (and publish deferred services) before their importers, so
// imports are resolved per mod, interleaved with initialization, in dependency order.
loader::sort_mods(m_mods);
// Config-disabled mods keep their dependency edges but must not resolve until enabled.
for (auto& mod : mods()) {
if (!mod.cvarIsEnabled->getValue()) {
log::write(mod.metadata.id, LOG_LEVEL_INFO, "disabled by config");
mod.active = false;
if (mod.servicesRegistered) {
svc::remove_services_for_provider(mod);
mod.servicesRegistered = false;
}
deactivate_mod(mod);
}
}
@@ -829,11 +845,6 @@ void ModLoader::flush_toasts() {
ui::push_toast(std::move(toast));
}
static bool requiredDepsActive(const LoadedMod& mod) {
return std::ranges::all_of(mod.dependencies,
[](const ModDependencyEdge& edge) { return !edge.required || edge.mod->active; });
}
std::vector<LoadedMod*> ModLoader::collect_lifecycle_set(LoadedMod& target) {
std::vector included{&target};
std::vector pending{&target};
@@ -983,8 +994,14 @@ void ModLoader::apply_lifecycle_change(LoadedMod& target, const bool reload) {
if (!ensure_native_loaded(*mod)) {
continue;
}
if (mod->native && !mod->servicesRegistered && register_static_service_exports(*mod)) {
mod->servicesRegistered = true;
if (mod->native && !mod->servicesRegistered) {
if (register_static_service_exports(*mod)) {
mod->servicesRegistered = true;
} else {
log::write(
mod->metadata.id, LOG_LEVEL_ERROR, "failed to register service exports");
deactivate_mod(*mod);
}
}
}
@@ -993,7 +1010,7 @@ void ModLoader::apply_lifecycle_change(LoadedMod& target, const bool reload) {
if (mod->active || mod->loadFailed || !mod->cvarIsEnabled->getValue()) {
continue;
}
if (!requiredDepsActive(*mod)) {
if (!required_deps_active(*mod)) {
mod->suspendedByProvider = true;
log::write(
mod->metadata.id, LOG_LEVEL_INFO, "suspended: a required provider is disabled");