import 'dart:async'; import 'package:rimworld_modman/logger.dart'; import 'package:rimworld_modman/mod.dart'; import 'package:rimworld_modman/mod_list.dart'; /// A class that helps find the minimum set of mods that exhibit a bug. /// /// Provides two main algorithms: /// - Binary search / bisect: Divides mods into smaller subsets to find problematic ones quickly. /// - Linear search / batching: Tests mods in small groups to systematically identify issues. /// /// These approaches help RimWorld mod users identify which mods are causing problems /// when many mods are installed. class ModListTroubleshooter { final ModList originalModList; ModList currentModList; // These indices should ALWAYS represent the CURRENT selection of mods int _startIndex = 0; int _endIndex = 0; ModListTroubleshooter(ModList modList) : originalModList = modList, currentModList = modList.copyWith(), _startIndex = 0, _endIndex = modList.activeMods.length; ModList binaryForward() { final midIndex = (_startIndex + _endIndex) ~/ 2; final subset = originalModList.activeMods.keys.toList().sublist( midIndex, _endIndex, ); currentModList.disableAll(); currentModList.enableMods(subset); _startIndex = midIndex; return currentModList; } ModList binaryBackward() { final midIndex = ((_startIndex + _endIndex) / 2).ceil(); final subset = originalModList.activeMods.keys.toList().sublist( _startIndex, midIndex, ); currentModList.disableAll(); currentModList.enableMods(subset); _endIndex = midIndex; return currentModList; } // If the current selection is not equal to our proposed step size // We do not MOVE but instead just return the correct amount of mods from the start ModList linearForward({int stepSize = 20}) { // If we are not "in step" if (_endIndex - _startIndex == stepSize) { // Move the indices forward by the step size, step forward _startIndex += stepSize; _endIndex += stepSize; } else { // Correct the end index to be in step _endIndex = _startIndex + stepSize; } if (_endIndex > originalModList.activeMods.length) { // If we are at the end of the list, move the start index such that we return // At most the step size amount of mods _endIndex = originalModList.activeMods.length; _startIndex = (_endIndex - stepSize).clamp(0, _endIndex); } final subset = originalModList.activeMods.keys.toList().sublist( _startIndex, _endIndex, ); currentModList.disableAll(); currentModList.enableMods(subset); return currentModList; } ModList linearBackward({int stepSize = 20}) { if (_endIndex - _startIndex == stepSize) { _startIndex -= stepSize; _endIndex -= stepSize; } else { _startIndex = _endIndex - stepSize; } if (_startIndex < 0) { _startIndex = 0; _endIndex = stepSize.clamp(0, originalModList.activeMods.length); } final subset = originalModList.activeMods.keys.toList().sublist( _startIndex, _endIndex, ); currentModList.disableAll(); currentModList.enableMods(subset); return currentModList; } void reset() { currentModList = originalModList.copyWith(); } }