using System; using System.Collections.Generic; using System.Linq; namespace ShrinkModFramework { internal static class ShrinkModDependencyGraph { internal static void ValidateVersion(ShrinkModInfo owner, ShrinkModDependency dependency, string actual) { if (!string.IsNullOrWhiteSpace(dependency.VersionRange) && !ShrinkModVersionRange.Parse(dependency.VersionRange).Contains(actual)) throw new InvalidOperationException($"Mod {owner.ModId} requires {dependency.ModId} in range {dependency.VersionRange}, but found {actual}."); } internal static IEnumerable Predecessors(string id, IEnumerable infos) { var map = infos.ToDictionary(info => info.ModId, StringComparer.Ordinal); foreach (var owner in map.Values) foreach (var dependency in owner.Dependencies) { if (!map.ContainsKey(dependency.ModId)) continue; if (owner.ModId == id && dependency.Ordering == ShrinkModDependencyOrdering.After) yield return dependency.ModId; if (dependency.ModId == id && dependency.Ordering == ShrinkModDependencyOrdering.Before) yield return owner.ModId; } } internal static IReadOnlyList Order(IEnumerable infos) { var map = infos.ToDictionary(info => info.ModId, StringComparer.Ordinal); foreach (var owner in map.Values) foreach (var dependency in owner.Dependencies) { if (map.TryGetValue(dependency.ModId, out var target)) ValidateVersion(owner, dependency, target.Version); else if (!dependency.Optional) throw new InvalidOperationException($"Mod {owner.ModId} is missing required dependency {dependency.ModId}."); } var result = new List(); var visiting = new HashSet(StringComparer.Ordinal); var visited = new HashSet(StringComparer.Ordinal); void Visit(string id) { if (visited.Contains(id)) return; if (!visiting.Add(id)) throw new InvalidOperationException($"Circular mod dependency/Before/After ordering includes {id}."); foreach (var predecessor in Predecessors(id, map.Values).OrderBy(value => value, StringComparer.Ordinal)) Visit(predecessor); visiting.Remove(id); visited.Add(id); result.Add(map[id]); } foreach (var info in map.Values.OrderBy(info => info.LoadOrder).ThenBy(info => info.ModId, StringComparer.Ordinal)) Visit(info.ModId); return result; } internal static bool SameMetadata(ShrinkModInfo a, ShrinkModInfo b) => a.ModId == b.ModId && a.DisplayName == b.DisplayName && a.Version == b.Version && a.LoadOrder == b.LoadOrder && a.EntryType == b.EntryType && a.AutoApplyHarmonyPatches == b.AutoApplyHarmonyPatches && a.Dependencies.Count == b.Dependencies.Count && a.Dependencies.Zip(b.Dependencies, (x, y) => x.ModId == y.ModId && x.VersionRange == y.VersionRange && x.Optional == y.Optional && x.Ordering == y.Ordering).All(equal => equal); } }