Use fucking graphs again
This commit is contained in:
@@ -255,34 +255,145 @@ class ModList {
|
||||
LoadOrder generateLoadOrder([LoadOrder? loadOrder]) {
|
||||
loadOrder ??= LoadOrder();
|
||||
final logger = Logger.instance;
|
||||
logger.info('Generating load order...');
|
||||
|
||||
for (final mod in activeMods.values) {
|
||||
logger.info('Checking mod: ${mod.id}');
|
||||
logger.info('Mod details: ${mod.toString()}');
|
||||
for (final incomp in mod.incompatibilities) {
|
||||
if (activeMods.containsKey(incomp)) {
|
||||
loadOrder.errors.add(
|
||||
'Incompatibility detected: ${mod.id} is incompatible with $incomp',
|
||||
);
|
||||
logger.warning(
|
||||
'Incompatibility detected: ${mod.id} is incompatible with $incomp',
|
||||
);
|
||||
} else {
|
||||
logger.info('No incompatibility found for: $incomp');
|
||||
}
|
||||
}
|
||||
for (final dep in mod.dependencies) {
|
||||
if (!activeMods.containsKey(dep)) {
|
||||
loadOrder.errors.add('Missing dependency: ${mod.id} requires $dep');
|
||||
logger.warning('Missing dependency: ${mod.id} requires $dep');
|
||||
} else {
|
||||
logger.info('Dependency found: ${mod.id} requires $dep');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Adding active mods to load order...');
|
||||
loadOrder.order.addAll(activeMods.values.toList());
|
||||
logger.info(
|
||||
'Active mods added: ${loadOrder.order.map((mod) => mod.id).join(', ')}',
|
||||
);
|
||||
|
||||
loadOrder.order.sort((a, b) {
|
||||
if (a.isBaseGame && !b.isBaseGame) return -1;
|
||||
if (!a.isBaseGame && b.isBaseGame) return 1;
|
||||
if (a.isExpansion && !b.isExpansion) return -1;
|
||||
if (!a.isExpansion && b.isExpansion) return 1;
|
||||
final modMap = {for (final mod in loadOrder.order) mod.id: mod};
|
||||
final graph = <String, Set<String>>{};
|
||||
final inDegree = <String, int>{};
|
||||
|
||||
// Step 1: Initialize graph and inDegree
|
||||
for (final mod in loadOrder.order) {
|
||||
graph[mod.id] = <String>{};
|
||||
inDegree[mod.id] = 0;
|
||||
}
|
||||
|
||||
// Step 2: Build dependency graph
|
||||
void addEdge(String from, String to) {
|
||||
final fromMod = modMap[from];
|
||||
if (fromMod == null) {
|
||||
logger.warning('Missing dependency: $from');
|
||||
return;
|
||||
}
|
||||
final toMod = modMap[to];
|
||||
if (toMod == null) {
|
||||
logger.warning('Missing dependency: $to');
|
||||
return;
|
||||
}
|
||||
if (graph[from]!.add(to)) {
|
||||
inDegree[to] = inDegree[to]! + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (final mod in loadOrder.order) {
|
||||
for (final target in mod.loadBefore) {
|
||||
addEdge(mod.id, target);
|
||||
}
|
||||
for (final target in mod.loadAfter) {
|
||||
addEdge(target, mod.id);
|
||||
}
|
||||
for (final dep in mod.dependencies) {
|
||||
addEdge(dep, mod.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Calculate tiers dynamically with cross-tier dependencies
|
||||
final tiers = <Mod, int>{};
|
||||
for (final mod in loadOrder.order) {
|
||||
int tier = 2; // Default to Tier 2
|
||||
|
||||
// Check if mod loads before any base game mod (Tier 0)
|
||||
final loadsBeforeBase = mod.loadBefore.any(
|
||||
(id) => modMap[id]?.isBaseGame ?? false,
|
||||
);
|
||||
if (mod.isBaseGame || loadsBeforeBase) {
|
||||
tier = 0;
|
||||
} else {
|
||||
// Check if mod loads before any expansion (Tier 1)
|
||||
final loadsBeforeExpansion = mod.loadBefore.any(
|
||||
(id) => modMap[id]?.isExpansion ?? false,
|
||||
);
|
||||
if (mod.isExpansion || loadsBeforeExpansion) {
|
||||
tier = 1;
|
||||
}
|
||||
}
|
||||
|
||||
tiers[mod] = tier;
|
||||
}
|
||||
|
||||
// Step 4: Global priority queue (tier ascending, size descending)
|
||||
final pq = PriorityQueue<Mod>((a, b) {
|
||||
final tierA = tiers[a]!;
|
||||
final tierB = tiers[b]!;
|
||||
if (tierA != tierB) return tierA.compareTo(tierB);
|
||||
return b.size.compareTo(a.size);
|
||||
});
|
||||
|
||||
Map<String, List<Mod>> relations = {};
|
||||
for (int i = loadOrder.order.length - 1; i >= 0; i--) {
|
||||
final mod = loadOrder!.order[i];
|
||||
logger.info('Processing mod: ${mod.id}');
|
||||
loadOrder = shuffleMod(mod, loadOrder, relations);
|
||||
// Initialize queue with mods having inDegree 0
|
||||
for (final mod in loadOrder.order) {
|
||||
if (inDegree[mod.id] == 0) {
|
||||
pq.add(mod);
|
||||
}
|
||||
}
|
||||
|
||||
return loadOrder!;
|
||||
final orderedMods = <Mod>[];
|
||||
while (pq.isNotEmpty) {
|
||||
final current = pq.removeFirst();
|
||||
orderedMods.add(current);
|
||||
|
||||
for (final neighborId in graph[current.id]!) {
|
||||
inDegree[neighborId] = inDegree[neighborId]! - 1;
|
||||
if (inDegree[neighborId] == 0) {
|
||||
final neighbor = modMap[neighborId]!;
|
||||
pq.add(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (orderedMods.length != loadOrder.order.length) {
|
||||
loadOrder.errors.add('Cycle detected in dependencies');
|
||||
logger.warning(
|
||||
'Cycle detected in dependencies: expected ${loadOrder.order.length}, got ${orderedMods.length}.',
|
||||
);
|
||||
}
|
||||
|
||||
loadOrder.order = orderedMods;
|
||||
logger.info(
|
||||
'Load order generated successfully with ${loadOrder.order.length} mods.',
|
||||
);
|
||||
for (final mod in loadOrder.order) {
|
||||
logger.info('Mod: ${mod.toString()}');
|
||||
}
|
||||
return loadOrder;
|
||||
}
|
||||
|
||||
// The point of relations and the recursive call is to handle the case where
|
||||
|
Reference in New Issue
Block a user