Files
flutter-rimworld-modman/lib/mod_list_troubleshooter.dart
2025-03-17 20:53:38 +01:00

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();
}
}