public static void Sort() { // ReSharper disable once InvalidXmlDocComment /** * Topological sort. * Depth first, because it's the easiest to understand. * https://en.wikipedia.org/wiki/Topological_sorting * * L ← Empty list that will contain the sorted nodes // we'll use done. * while exists nodes without a permanent mark do * select an unmarked node n * visit(n) * * function visit(node n) * if n has a permanent mark then // is in done list * return * if n has a temporary mark then // is in progress list * stop (not a DAG) * * mark n with a temporary mark // add to progress list * * for each node m with an edge from n to m do // visit each dependency * visit(m) * * remove temporary mark from n // remove from progress list * mark n with a permanent mark // add to done list * add n to head of L */ var graph = new Dictionary <ModButton, HashSet <ModButton> >(); var done = new HashSet <ModButton>(); var progress = new HashSet <ModButton>(); // create a directed acyclic graph. foreach (var activeButton in ActiveButtons) { if (!graph.ContainsKey(activeButton)) { graph[activeButton] = new HashSet <ModButton>(); } if (!(activeButton is ModButton_Installed installedActiveButton)) { continue; } foreach (var target in installedActiveButton.Manifest.LoadBefore .Select(d => d.Target) .Where(t => t != null)) { var targetButton = ModButton_Installed.For(target); if (!graph.ContainsKey(targetButton)) { graph[targetButton] = new HashSet <ModButton>(); } graph[targetButton].Add(activeButton); } foreach (var target in installedActiveButton.Manifest.LoadAfter .Concat(installedActiveButton.Manifest.Dependencies) .Select(d => d.Target) .Where(t => t != null)) { var targetButton = ModButton_Installed.For(target); graph[activeButton].Add(targetButton); if (!graph.ContainsKey(targetButton)) { graph[targetButton] = new HashSet <ModButton>(); } } } // do that sort foreach (var activeButton in ActiveButtons) { var success = Sort_Visit(activeButton, graph, ref done, ref progress); if (!success) { // we have a cyclic dependency. Messages.Message(I18n.SortFailed_Cyclic(activeButton.Name, success.Reason), MessageTypeDefOf.CautionInput, false); return; } } // reset mod list, then add mods back in order. SoundDefOf.Tick_High.PlayOneShotOnCamera(); Reset(false); foreach (var mod in done) { // try to avoid re-caching too many times. if (mod is ModButton_Installed installed) { installed.Selected.Active = true; } else { mod.Active = true; } TryAdd(mod, false); } Notify_ModListChanged(); }