Prioritize larger mods

This commit is contained in:
2025-03-15 23:17:21 +01:00
parent 7e4bee3482
commit 74eec3f3cc
2 changed files with 140 additions and 49 deletions

View File

@@ -292,7 +292,7 @@ class _LoadOrderPageState extends State<LoadOrderPage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Automatically sort mods based on dependencies.', 'Automatically sort mods based on dependencies, prioritizing larger mods.',
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -302,6 +302,14 @@ class _LoadOrderPageState extends State<LoadOrderPage> {
onPressed: _isLoading ? null : _sortMods, onPressed: _isLoading ? null : _sortMods,
child: const Text('Auto-sort Mods'), child: const Text('Auto-sort Mods'),
), ),
const SizedBox(width: 8),
Chip(
backgroundColor: Colors.amber.withOpacity(0.2),
label: const Text(
'Larger mods prioritized',
style: TextStyle(fontSize: 12),
),
),
const SizedBox(width: 16), const SizedBox(width: 16),
ElevatedButton( ElevatedButton(
onPressed: _isLoading || _sortedMods.isEmpty ? null : _saveModOrder, onPressed: _isLoading || _sortedMods.isEmpty ? null : _saveModOrder,
@@ -355,6 +363,45 @@ class _LoadOrderPageState extends State<LoadOrderPage> {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
if (_sortedMods.isNotEmpty)
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text('Legend:', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Row(
children: [
Icon(Icons.link, color: Colors.orange, size: 16),
const SizedBox(width: 4),
Text('Hard dependencies', style: TextStyle(fontSize: 12)),
const SizedBox(width: 12),
Icon(Icons.link_off, color: Colors.blue, size: 16),
const SizedBox(width: 4),
Text('Soft dependencies', style: TextStyle(fontSize: 12)),
],
),
const SizedBox(height: 4),
Row(
children: [
Container(width: 12, height: 12, color: Colors.amber),
const SizedBox(width: 4),
Text('Very large mod (>1000 files)', style: TextStyle(fontSize: 12)),
const SizedBox(width: 12),
Container(width: 12, height: 12, color: Colors.yellow.shade600),
const SizedBox(width: 4),
Text('Large mod (>500 files)', style: TextStyle(fontSize: 12)),
],
),
],
),
),
Expanded( Expanded(
child: _sortedMods.isEmpty child: _sortedMods.isEmpty
? Center( ? Center(
@@ -370,23 +417,53 @@ class _LoadOrderPageState extends State<LoadOrderPage> {
return Card( return Card(
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile( child: ListTile(
leading: Text('${index + 1}'), leading: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${index + 1}'),
const SizedBox(height: 2),
if (mod.size > 0) Text('${mod.size}', style: TextStyle(fontSize: 10, color: Colors.grey)),
],
),
title: Text(mod.name), title: Text(mod.name),
subtitle: Text(mod.id), subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(mod.id, style: TextStyle(fontSize: 10)),
if (mod.hardDependencies.isNotEmpty)
Text(
'Hard dependencies: ${mod.hardDependencies.length}',
style: TextStyle(fontSize: 10, color: Colors.orange),
),
if (mod.softDependencies.isNotEmpty)
Text(
'Soft dependencies: ${mod.softDependencies.length}',
style: TextStyle(fontSize: 10, color: Colors.blue),
),
],
),
isThreeLine: mod.hardDependencies.isNotEmpty || mod.softDependencies.isNotEmpty,
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (mod.hardDependencies.isNotEmpty) if (mod.hardDependencies.isNotEmpty)
Tooltip( Icon(Icons.link, color: Colors.orange, size: 16),
message: 'Hard dependencies: ${mod.hardDependencies.length}',
child: Icon(Icons.link, color: Colors.orange, size: 16),
),
const SizedBox(width: 4), const SizedBox(width: 4),
if (mod.softDependencies.isNotEmpty) if (mod.softDependencies.isNotEmpty)
Tooltip( Icon(Icons.link_off, color: Colors.blue, size: 16),
message: 'Soft dependencies: ${mod.softDependencies.length}', const SizedBox(width: 4),
child: Icon(Icons.link_off, color: Colors.blue, size: 16), Text(
'${mod.size} files',
style: TextStyle(
fontSize: 12,
fontWeight: mod.size > 1000 ? FontWeight.bold : FontWeight.normal,
color: mod.size > 1000
? Colors.amber
: mod.size > 500
? Colors.yellow.shade600
: Colors.grey,
), ),
),
], ],
), ),
), ),

View File

@@ -332,7 +332,7 @@ class ModList {
return null; // No cycle found return null; // No cycle found
} }
// Perform a topological sort using Kahn's algorithm // Perform a topological sort using Kahn's algorithm with size prioritization
List<String> topologicalSort(Map<String, Set<String>> graph) { List<String> topologicalSort(Map<String, Set<String>> graph) {
// Create a copy of the graph to work with // Create a copy of the graph to work with
final Map<String, Set<String>> graphCopy = {}; final Map<String, Set<String>> graphCopy = {};
@@ -352,54 +352,68 @@ class ModList {
} }
} }
// Start with nodes that have no dependencies (in-degree = 0) // Separate nodes by "layers" (nodes that can be processed at the same time)
List<String> nodesWithNoDependencies = []; List<List<String>> layers = [];
for (final node in inDegree.keys) {
if (inDegree[node] == 0) { // Process until all nodes are assigned to layers
nodesWithNoDependencies.add(node); while (inDegree.isNotEmpty) {
// Find all nodes with in-degree 0 in this iteration
List<String> currentLayer = [];
inDegree.forEach((node, degree) {
if (degree == 0) {
currentLayer.add(node);
}
});
if (currentLayer.isEmpty && inDegree.isNotEmpty) {
// We have a cycle - add all remaining nodes to a final layer
currentLayer = inDegree.keys.toList();
print("Warning: Cycle detected in dependency graph. Adding all remaining nodes to final layer.");
}
// Sort this layer by mod size (descending)
currentLayer.sort((a, b) {
final modA = mods[a];
final modB = mods[b];
if (modA == null || modB == null) return 0;
return modB.size.compareTo(modA.size); // Larger mods first
});
// Add the layer to our layers list
layers.add(currentLayer);
// Remove processed nodes from inDegree
for (final node in currentLayer) {
inDegree.remove(node);
// Update in-degrees for remaining nodes
for (final entry in graphCopy.entries) {
if (entry.value.contains(node)) {
if (inDegree.containsKey(entry.key)) {
inDegree[entry.key] = inDegree[entry.key]! - 1;
}
}
}
} }
} }
// Result will store the topological order // Flatten the layers to get the final order (first layer first)
List<String> result = []; List<String> result = [];
for (final layer in layers) {
// Process nodes with no dependencies result.addAll(layer);
while (nodesWithNoDependencies.isNotEmpty) {
final node = nodesWithNoDependencies.removeLast();
result.add(node);
// For each node that depends on this one, decrement its in-degree
final dependents = [];
for (final entry in graphCopy.entries) {
if (entry.value.contains(node)) {
dependents.add(entry.key);
}
}
for (final dependent in dependents) {
graphCopy[dependent]!.remove(node);
inDegree[dependent] = inDegree[dependent]! - 1;
if (inDegree[dependent] == 0) {
nodesWithNoDependencies.add(dependent);
}
}
} }
// Check if we have a valid topological sort // Final sanity check to make sure all nodes are included
if (result.length != graph.keys.length) { if (result.length != graph.keys.length) {
print( // Add any missing nodes
"Warning: Cyclic dependency detected, topological sort may be incomplete",
);
// Add any remaining nodes to keep all mods
for (final node in graph.keys) { for (final node in graph.keys) {
if (!result.contains(node)) { if (!result.contains(node)) {
result.add(node); result.add(node);
} }
} }
} }
return result.reversed.toList(); // Reverse to get correct load order return result;
} }
// Adjust the order to respect soft dependencies where possible // Adjust the order to respect soft dependencies where possible
@@ -478,7 +492,7 @@ class ModList {
print("Will attempt to break cycle to produce a valid load order"); print("Will attempt to break cycle to produce a valid load order");
} }
print("Performing topological sort for hard dependencies..."); print("Performing topological sort for hard dependencies (prioritizing larger mods)...");
final hardOrder = topologicalSort(hardGraph); final hardOrder = topologicalSort(hardGraph);
print("Adjusting for soft dependencies..."); print("Adjusting for soft dependencies...");