Prioritize larger mods
This commit is contained in:
@@ -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,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@@ -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,46 +352,60 @@ 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);
|
||||||
@@ -399,7 +413,7 @@ class ModList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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...");
|
||||||
|
Reference in New Issue
Block a user