140 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			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 Move {
 | 
						|
  final int startIndex;
 | 
						|
  final int endIndex;
 | 
						|
 | 
						|
  Move({required this.startIndex, required this.endIndex});
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
  Move binaryForwardMove() {
 | 
						|
    final midIndex = (_startIndex + _endIndex) ~/ 2;
 | 
						|
    return Move(startIndex: midIndex, endIndex: _endIndex);
 | 
						|
  }
 | 
						|
 | 
						|
  Move binaryBackwardMove() {
 | 
						|
    final midIndex = ((_startIndex + _endIndex) / 2).ceil();
 | 
						|
    return Move(startIndex: _startIndex, endIndex: midIndex);
 | 
						|
  }
 | 
						|
 | 
						|
  ModList binaryForward() {
 | 
						|
    final move = binaryForwardMove();
 | 
						|
    final subset = originalModList.activeMods.keys.toList().sublist(
 | 
						|
      move.startIndex,
 | 
						|
      move.endIndex,
 | 
						|
    );
 | 
						|
    currentModList.disableAll();
 | 
						|
    currentModList.enableMods(subset);
 | 
						|
    _startIndex = move.startIndex;
 | 
						|
    _endIndex = move.endIndex;
 | 
						|
    return currentModList;
 | 
						|
  }
 | 
						|
 | 
						|
  ModList binaryBackward() {
 | 
						|
    final move = binaryBackwardMove();
 | 
						|
    final subset = originalModList.activeMods.keys.toList().sublist(
 | 
						|
      move.startIndex,
 | 
						|
      move.endIndex,
 | 
						|
    );
 | 
						|
    currentModList.disableAll();
 | 
						|
    currentModList.enableMods(subset);
 | 
						|
    _startIndex = move.startIndex;
 | 
						|
    _endIndex = move.endIndex;
 | 
						|
    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
 | 
						|
  Move linearForwardMove({int stepSize = 20}) {
 | 
						|
    var start = _startIndex;
 | 
						|
    var end = _endIndex;
 | 
						|
    // If we are not "in step"
 | 
						|
    if (end - start == stepSize) {
 | 
						|
      // Move the indices forward by the step size, step forward
 | 
						|
      start += stepSize;
 | 
						|
      end += stepSize;
 | 
						|
    } else {
 | 
						|
      end = start + stepSize;
 | 
						|
    }
 | 
						|
 | 
						|
    if (end > 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
 | 
						|
      end = originalModList.activeMods.length;
 | 
						|
      start = (end - stepSize).clamp(0, end);
 | 
						|
    }
 | 
						|
 | 
						|
    return Move(startIndex: start, endIndex: end);
 | 
						|
  }
 | 
						|
 | 
						|
  Move linearBackwardMove({int stepSize = 20}) {
 | 
						|
    var start = _startIndex;
 | 
						|
    var end = _endIndex;
 | 
						|
    if (end - start == stepSize) {
 | 
						|
      start -= stepSize;
 | 
						|
      end -= stepSize;
 | 
						|
    } else {
 | 
						|
      start = end - stepSize;
 | 
						|
    }
 | 
						|
 | 
						|
    if (start < 0) {
 | 
						|
      start = 0;
 | 
						|
      end = stepSize.clamp(0, originalModList.activeMods.length);
 | 
						|
    }
 | 
						|
    return Move(startIndex: start, endIndex: end);
 | 
						|
  }
 | 
						|
 | 
						|
  ModList linearForward({int stepSize = 20}) {
 | 
						|
    final move = linearForwardMove(stepSize: stepSize);
 | 
						|
    final subset = originalModList.activeMods.keys.toList().sublist(
 | 
						|
      move.startIndex,
 | 
						|
      move.endIndex,
 | 
						|
    );
 | 
						|
 | 
						|
    currentModList.disableAll();
 | 
						|
    currentModList.enableMods(subset);
 | 
						|
    _startIndex = move.startIndex;
 | 
						|
    _endIndex = move.endIndex;
 | 
						|
    return currentModList;
 | 
						|
  }
 | 
						|
 | 
						|
  ModList linearBackward({int stepSize = 20}) {
 | 
						|
    final move = linearBackwardMove(stepSize: stepSize);
 | 
						|
    final subset = originalModList.activeMods.keys.toList().sublist(
 | 
						|
      move.startIndex,
 | 
						|
      move.endIndex,
 | 
						|
    );
 | 
						|
 | 
						|
    currentModList.disableAll();
 | 
						|
    currentModList.enableMods(subset);
 | 
						|
    _startIndex = move.startIndex;
 | 
						|
    _endIndex = move.endIndex;
 | 
						|
    return currentModList;
 | 
						|
  }
 | 
						|
 | 
						|
  void reset() {
 | 
						|
    currentModList = originalModList.copyWith();
 | 
						|
  }
 | 
						|
}
 |