diff --git a/lib/mod_list.dart b/lib/mod_list.dart index 3583545..61fda42 100644 --- a/lib/mod_list.dart +++ b/lib/mod_list.dart @@ -6,8 +6,9 @@ import 'package:rimworld_modman/mod.dart'; import 'package:xml/xml.dart'; class LoadOrder { - final List loadOrder = []; + final List order = []; final List errors = []; + List get loadOrder => order.map((e) => e.id).toList(); LoadOrder(); @@ -18,7 +19,7 @@ class ModList { String configPath = ''; String modsPath = ''; // O(1) lookup - Map activeMods = {}; + Map activeMods = {}; Map mods = {}; ModList({this.configPath = '', this.modsPath = ''}); @@ -202,9 +203,10 @@ class ModList { void setEnabled(String modId, bool enabled) { if (mods.containsKey(modId)) { - mods[modId]!.enabled = enabled; + final mod = mods[modId]!; + mod.enabled = enabled; if (enabled) { - activeMods[modId] = true; + activeMods[modId] = mod; } else { activeMods.remove(modId); } @@ -247,9 +249,10 @@ class ModList { // return generateLoadOrder(loadOrder); //} - LoadOrder generateLoadOrder() { - final modMap = {for (final m in mods.values) m.id: m}; - _validateIncompatibilities(mods.values.toList()); + LoadOrder generateLoadOrder([LoadOrder? loadOrder]) { + loadOrder ??= LoadOrder(); + final modMap = {for (final m in activeMods.values) m.id: m}; + _validateIncompatibilities(loadOrder); // Hard dependency graph final inDegree = {}; @@ -260,7 +263,7 @@ class ModList { final reverseLoadAfter = >{}; // Initialize data structures - for (final mod in mods.values) { + for (final mod in activeMods.values) { mod.loadBeforeNotPlaced = mod.loadBefore.length; mod.loadAfterPlaced = 0; @@ -271,7 +274,7 @@ class ModList { } // Build dependency graph and reverse soft constraints - for (final mod in mods.values) { + for (final mod in activeMods.values) { for (final depId in mod.dependencies) { adjacency[depId]!.add(mod.id); inDegree[mod.id] = (inDegree[mod.id] ?? 0) + 1; @@ -318,17 +321,15 @@ class ModList { }); // Initialize heap with available mods - for (final modId in activeMods.keys) { - final mod = modMap[modId]; - if (mod != null && inDegree[modId] == 0) { + for (final mod in activeMods.values) { + if (inDegree[mod.id] == 0) { heap.add(mod); } } - final sortedMods = []; while (heap.isNotEmpty) { final current = heap.removeFirst(); - sortedMods.add(current); + loadOrder.order.add(current); // Update dependents' in-degree for (final neighborId in adjacency[current.id]!) { @@ -342,34 +343,32 @@ class ModList { _updateReverseConstraints( current, reverseLoadBefore, - sortedMods, + loadOrder, heap, (mod) => mod.loadBeforeNotPlaced--, ); _updateReverseConstraints( current, reverseLoadAfter, - sortedMods, + loadOrder, heap, (mod) => mod.loadAfterPlaced++, ); } - if (sortedMods.length != mods.length) { - throw Exception("Cyclic dependencies detected"); + if (loadOrder.order.length != activeMods.length) { + loadOrder.errors.add("Cyclic dependencies detected"); } - final loadOrder = LoadOrder(); - loadOrder.loadOrder.addAll(sortedMods.map((e) => e.id)); return loadOrder; } - void _validateIncompatibilities(List mods) { - final enabledMods = mods.where((m) => m.enabled).toList(); + void _validateIncompatibilities(LoadOrder loadOrder) { + final enabledMods = loadOrder.order.where((m) => m.enabled).toList(); for (final mod in enabledMods) { for (final incompatibleId in mod.incompatibilities) { if (enabledMods.any((m) => m.id == incompatibleId)) { - throw Exception("Conflict: ${mod.id} vs $incompatibleId"); + loadOrder.errors.add("Incompatible mods: ${mod.id} and $incompatibleId"); } } } @@ -378,12 +377,12 @@ class ModList { void _updateReverseConstraints( Mod current, Map> reverseMap, - List sortedMods, + LoadOrder loadOrder, PriorityQueue heap, void Function(Mod) update, ) { reverseMap[current.id]?.forEach((affectedMod) { - if (!sortedMods.contains(affectedMod)) { + if (!loadOrder.order.contains(affectedMod)) { update(affectedMod); // If mod is already in heap, re-add to update position if (heap.contains(affectedMod)) { @@ -396,7 +395,7 @@ class ModList { LoadOrder loadRequired() { final loadOrder = generateLoadOrder(); - for (final modId in loadOrder.loadOrder) { + for (final modId in loadOrder.order.map((e) => e.id)) { setEnabled(modId, true); } return loadOrder;