package com.hypixel.hytale.component.dependency; import com.hypixel.hytale.component.ComponentRegistry; import com.hypixel.hytale.component.system.ISystem; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class DependencyGraph { @Nonnull private final ISystem[] systems; private final Map, List>> beforeSystemEdges = new Object2ObjectOpenHashMap(); private final Map, List>> afterSystemEdges = new Object2ObjectOpenHashMap(); private final Map, Set>> afterSystemUnfulfilledEdges = new Object2ObjectOpenHashMap(); private DependencyGraph.Edge[] edges = DependencyGraph.Edge.emptyArray(); public DependencyGraph(@Nonnull ISystem[] systems) { this.systems = systems; for (int i = 0; i < systems.length; i++) { ISystem system = systems[i]; this.beforeSystemEdges.put(system, new ObjectArrayList()); this.afterSystemEdges.put(system, new ObjectArrayList()); this.afterSystemUnfulfilledEdges.put(system, new HashSet<>()); } } @Nonnull public ISystem[] getSystems() { return this.systems; } public void resolveEdges(@Nonnull ComponentRegistry registry) { for (ISystem system : this.systems) { for (Dependency dependency : system.getDependencies()) { dependency.resolveGraphEdge(registry, system, this); } if (system.getGroup() != null) { for (Dependency dependency : system.getGroup().getDependencies()) { dependency.resolveGraphEdge(registry, system, this); } } } for (ISystem system : this.systems) { if (this.afterSystemEdges.get(system).isEmpty()) { int priority = 0; List> edges = this.beforeSystemEdges.get(system); for (DependencyGraph.Edge edge : edges) { priority += edge.priority / edges.size(); } this.addEdgeFromRoot(system, priority); } } } public void addEdgeFromRoot(@Nonnull ISystem afterSystem, int priority) { this.addEdge(new DependencyGraph.Edge<>(null, afterSystem, priority)); } public void addEdge(@Nonnull ISystem beforeSystem, @Nonnull ISystem afterSystem, int priority) { this.addEdge(new DependencyGraph.Edge<>(beforeSystem, afterSystem, priority)); } public void addEdge(@Nonnull DependencyGraph.Edge edge) { int index = Arrays.binarySearch(this.edges, edge); int insertionPoint; if (index >= 0) { insertionPoint = index; while (insertionPoint < this.edges.length && this.edges[insertionPoint].priority == edge.priority) { insertionPoint++; } } else { insertionPoint = -(index + 1); } int oldLength = this.edges.length; int newLength = oldLength + 1; if (oldLength < newLength) { this.edges = Arrays.copyOf(this.edges, newLength); } System.arraycopy(this.edges, insertionPoint, this.edges, insertionPoint + 1, oldLength - insertionPoint); this.edges[insertionPoint] = edge; if (edge.beforeSystem != null) { this.beforeSystemEdges.get(edge.beforeSystem).add(edge); } this.afterSystemEdges.get(edge.afterSystem).add(edge); if (!edge.fulfilled) { this.afterSystemUnfulfilledEdges.get(edge.afterSystem).add(edge); } } public void sort(ISystem[] sortedSystems) { int index = 0; label52: while (index < this.systems.length) { for (DependencyGraph.Edge edge : this.edges) { if (!edge.resolved && edge.fulfilled) { ISystem system = edge.afterSystem; if (this.afterSystemUnfulfilledEdges.get(system).isEmpty() && !this.hasEdgeOfLaterPriority(system, edge.priority)) { sortedSystems[index++] = system; this.resolveEdgesFor(system); this.fulfillEdgesFor(system); continue label52; } } } for (DependencyGraph.Edge edgex : this.edges) { if (!edgex.resolved && edgex.fulfilled) { ISystem system = edgex.afterSystem; if (this.afterSystemUnfulfilledEdges.get(system).isEmpty()) { sortedSystems[index++] = system; this.resolveEdgesFor(system); this.fulfillEdgesFor(system); continue label52; } } } throw new IllegalArgumentException("Found a cyclic dependency!" + this); } } private boolean hasEdgeOfLaterPriority(ISystem system, int priority) { for (DependencyGraph.Edge edge : this.afterSystemEdges.get(system)) { if (!edge.resolved && edge.priority > priority) { return true; } } return false; } private void resolveEdgesFor(ISystem system) { for (DependencyGraph.Edge edge : this.afterSystemEdges.get(system)) { edge.resolved = true; } } private void fulfillEdgesFor(ISystem system) { for (DependencyGraph.Edge edge : this.beforeSystemEdges.get(system)) { edge.fulfilled = true; this.afterSystemUnfulfilledEdges.get(edge.afterSystem).remove(edge); } } @Nonnull @Override public String toString() { return "DependencyGraph{systems=" + Arrays.toString((Object[])this.systems) + ", edges=" + Arrays.toString((Object[])this.edges) + "}"; } private static class Edge implements Comparable> { private static final DependencyGraph.Edge[] EMPTY_ARRAY = new DependencyGraph.Edge[0]; @Nullable private final ISystem beforeSystem; private final ISystem afterSystem; private final int priority; private boolean fulfilled; private boolean resolved; public static DependencyGraph.Edge[] emptyArray() { return (DependencyGraph.Edge[])EMPTY_ARRAY; } public Edge(@Nullable ISystem beforeSystem, ISystem afterSystem, int priority) { this.beforeSystem = beforeSystem; this.afterSystem = afterSystem; this.priority = priority; this.fulfilled = beforeSystem == null; } public int compareTo(@Nonnull DependencyGraph.Edge o) { return Integer.compare(this.priority, o.priority); } @Nonnull @Override public String toString() { return "Edge{beforeSystem=" + this.beforeSystem + ", afterSystem=" + this.afterSystem + ", priority=" + this.priority + ", fulfilled=" + this.fulfilled + ", resolved=" + this.resolved + "}"; } } }