From 74eec3f3cc8d228f4d558250d4bdec3ccb77b65f Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sat, 15 Mar 2025 23:17:21 +0100 Subject: [PATCH] Prioritize larger mods --- lib/main.dart | 97 +++++++++++++++++++++++++++++++++++++++++----- lib/modloader.dart | 92 ++++++++++++++++++++++++------------------- 2 files changed, 140 insertions(+), 49 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 03e04b4..d86b15a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -292,7 +292,7 @@ class _LoadOrderPageState extends State { ), const SizedBox(height: 16), Text( - 'Automatically sort mods based on dependencies.', + 'Automatically sort mods based on dependencies, prioritizing larger mods.', style: Theme.of(context).textTheme.bodyLarge, ), const SizedBox(height: 24), @@ -302,6 +302,14 @@ class _LoadOrderPageState extends State { onPressed: _isLoading ? null : _sortMods, 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), ElevatedButton( onPressed: _isLoading || _sortedMods.isEmpty ? null : _saveModOrder, @@ -355,6 +363,45 @@ class _LoadOrderPageState extends State { ), ), 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( child: _sortedMods.isEmpty ? Center( @@ -370,23 +417,53 @@ class _LoadOrderPageState extends State { return Card( margin: const EdgeInsets.symmetric(vertical: 4), 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), - 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( mainAxisSize: MainAxisSize.min, children: [ if (mod.hardDependencies.isNotEmpty) - Tooltip( - message: 'Hard dependencies: ${mod.hardDependencies.length}', - child: Icon(Icons.link, color: Colors.orange, size: 16), - ), + Icon(Icons.link, color: Colors.orange, size: 16), const SizedBox(width: 4), if (mod.softDependencies.isNotEmpty) - Tooltip( - message: 'Soft dependencies: ${mod.softDependencies.length}', - child: Icon(Icons.link_off, color: Colors.blue, size: 16), + Icon(Icons.link_off, color: Colors.blue, size: 16), + const SizedBox(width: 4), + 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, ), + ), ], ), ), diff --git a/lib/modloader.dart b/lib/modloader.dart index 084515b..66efb7e 100644 --- a/lib/modloader.dart +++ b/lib/modloader.dart @@ -332,7 +332,7 @@ class ModList { 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 topologicalSort(Map> graph) { // Create a copy of the graph to work with final Map> graphCopy = {}; @@ -352,54 +352,68 @@ class ModList { } } - // Start with nodes that have no dependencies (in-degree = 0) - List nodesWithNoDependencies = []; - for (final node in inDegree.keys) { - if (inDegree[node] == 0) { - nodesWithNoDependencies.add(node); + // Separate nodes by "layers" (nodes that can be processed at the same time) + List> layers = []; + + // Process until all nodes are assigned to layers + while (inDegree.isNotEmpty) { + // Find all nodes with in-degree 0 in this iteration + List 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 result = []; - - // Process nodes with no dependencies - 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); - } - } + for (final layer in layers) { + result.addAll(layer); } - - // Check if we have a valid topological sort + + // Final sanity check to make sure all nodes are included if (result.length != graph.keys.length) { - print( - "Warning: Cyclic dependency detected, topological sort may be incomplete", - ); - - // Add any remaining nodes to keep all mods + // Add any missing nodes for (final node in graph.keys) { if (!result.contains(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 @@ -478,7 +492,7 @@ class ModList { 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); print("Adjusting for soft dependencies...");