Skip to content

Commit

Permalink
Merge pull request #29 from ldtteam/feature/speed-up
Browse files Browse the repository at this point in the history
Improve performance of cycle detection and analysis
  • Loading branch information
marchermans authored Sep 9, 2024
2 parents 4177a46 + 3e197a3 commit 392cac0
Show file tree
Hide file tree
Showing 39 changed files with 845 additions and 252 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Aequivaleo

Library and mediator mod which allows for high-speed recipe and equivalency analysis in alchemy mods.
More documentation to follow, please join our discord for more information: https://discord.gg/7KxvDa86jW
More documentation to follow, please join our discord for more information: https://discord.gg/7KxvDa86jW
We will be adding a dedicated wiki for this mod soon.
7 changes: 7 additions & 0 deletions src/api/java/com/ldtteam/aequivaleo/api/IAequivaleoAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.ldtteam.aequivaleo.api.results.IEquivalencyResults;
import com.ldtteam.aequivaleo.api.results.IResultsAdapterHandlerRegistry;
import com.ldtteam.aequivaleo.api.results.IResultsInformationCache;
import com.ldtteam.aequivaleo.api.tag.ITagContentsRetriever;
import com.ldtteam.aequivaleo.api.util.Constants;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
Expand Down Expand Up @@ -194,6 +195,12 @@ default ModContainer getAequivaleoContainer() {
*/
<T extends IRegistryEntry, E extends IRegistryEntry> IRegistryView<E> createView(final IForgeRegistry<T> registry, final Function<T, Optional<E>> viewFilter);

/**
* A contents retriever for tags.
* @return The tag contents retriever.
*/
ITagContentsRetriever getTagContentsRetriever();

/**
* The inner api holder class which is responsible for setting up the api instance.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ public interface IEquivalencyRecipe extends Comparable<IEquivalencyRecipe>
*/
SortedSet<ICompoundContainer<?>> getOutputs();

/**
* Indicates if this recipe is a distributor.
* Distributors are recipes that take in a bunch of inputs, negotiate the results and then set that result on all outputs.
* This is useful for conversion recipes, like tags.
*
* @return {@code True} when a distributor.
*/
default boolean isDistributor() {
return false;
}

/**
* Returns the offset factor between inputs and outputs.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ static IResultsAdapterHandlerRegistry getInstance() {
*
* @return The registry.
*/
<T> IResultsAdapterHandlerRegistry registerHandler(final Predicate<Object> canHandlePredicate, final Function<T, Set<?>> alternativesProducer);
<T> IResultsAdapterHandlerRegistry registerHandler(final Predicate<T> canHandlePredicate, final Function<T, Set<?>> alternativesProducer);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.ldtteam.aequivaleo.api.tag;

import com.ldtteam.aequivaleo.api.IAequivaleoAPI;
import com.ldtteam.aequivaleo.api.compound.container.ICompoundContainer;
import net.minecraft.core.RegistryAccess;
import net.minecraft.tags.TagKey;

import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Predicate;

/**
* Allows for the retrieval of tag contents.
*/
public interface ITagContentsRetriever {

/**
* Gets the instance of the tag contents retriever.
*
* @return The instance of the tag contents retriever.
*/
static ITagContentsRetriever getInstance() {
return IAequivaleoAPI.getInstance().getTagContentsRetriever();
}

/**
* Registers a handler for a tag.
* Of all registered handlers: The first one that returns a non-empty set of contents will be used.
* <p>
* If no handler is registered for a tag, the tag will be considered empty.
*
* @param selector The selector for the tag.
* @param retriever The retriever for the tag.
* @param <T> The type of the tag.
*/
<T> void registerHandler(final Predicate<TagKey<T>> selector, final BiFunction<RegistryAccess, TagKey<T>, Set<ICompoundContainer<?>>> retriever);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@
import com.ldtteam.aequivaleo.api.util.AequivaleoLogger;
import com.ldtteam.aequivaleo.compound.container.registry.CompoundContainerFactoryManager;
import com.ldtteam.aequivaleo.compound.information.CompoundInformationRegistry;
import com.ldtteam.aequivaleo.results.ResultsAdapterHandlerRegistry;
import com.ldtteam.aequivaleo.utils.AnalysisLogHandler;
import com.ldtteam.aequivaleo.utils.WorldCacheUtils;
import com.ldtteam.aequivaleo.utils.WorldUtils;
import net.minecraft.world.level.Level;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.loading.FMLLoader;
import org.apache.commons.compress.utils.Lists;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand Down Expand Up @@ -131,16 +130,24 @@ public BuildRecipeGraph createGraph() {
}
}

final Set<INode> sourceNodes = new HashSet<>();

for (ICompoundContainer<?> valueWrapper : CompoundInformationRegistry.getInstance(primaryOwner.getIdentifier()).getValueInformation().keySet()) {
IResultsOwningNode node = constructContainerNode(valueWrapper, recipeGraph, compoundNodes);
recipeGraph.clearIncomingEdgesOf(node);
node.results().force(CompoundInstanceSet.of(CompoundInformationRegistry.getInstance(primaryOwner.getIdentifier()).getValueInformation().get(valueWrapper)));
final Set<ICompoundContainer<?>> alternatives = ResultsAdapterHandlerRegistry.getInstance().produceAlternatives(valueWrapper.getContents());

sourceNodes.add(forceContainerNodeValues(valueWrapper, valueWrapper, recipeGraph, compoundNodes));
for (ICompoundContainer<?> alternative : alternatives) {
sourceNodes.add(forceContainerNodeValues(alternative, valueWrapper, recipeGraph, compoundNodes));
}
}

for (ICompoundContainer<?> valueWrapper : CompoundInformationRegistry.getInstance(primaryOwner.getIdentifier()).getBaseInformation().keySet()) {
IResultsOwningNode node = constructContainerNode(valueWrapper, recipeGraph, compoundNodes);
recipeGraph.clearIncomingEdgesOf(node);
node.results().base(CompoundInstanceSet.of(CompoundInformationRegistry.getInstance(primaryOwner.getIdentifier()).getBaseInformation().get(valueWrapper)));
final Set<ICompoundContainer<?>> alternatives = ResultsAdapterHandlerRegistry.getInstance().produceAlternatives(valueWrapper.getContents());

sourceNodes.add(baseContainerNodeValues(valueWrapper, valueWrapper, recipeGraph, compoundNodes));
for (ICompoundContainer<?> alternative : alternatives) {
sourceNodes.add(baseContainerNodeValues(alternative, valueWrapper, recipeGraph, compoundNodes));
}
}

if (Aequivaleo.getInstance().getConfiguration().getServer().exportGraph.get()) {
Expand All @@ -159,7 +166,7 @@ public BuildRecipeGraph createGraph() {
final SourceNode source = new SourceNode();
recipeGraph.addVertex(source);

for (IContainerNode rootNode : rootNodes) {
for (INode rootNode : sourceNodes) {
recipeGraph.addEdge(source, rootNode);
recipeGraph.setEdgeWeight(source, rootNode, 1d);
}
Expand All @@ -173,6 +180,18 @@ public BuildRecipeGraph createGraph() {
source);
}

private INode baseContainerNodeValues(ICompoundContainer<?> valueWrapper, ICompoundContainer<?> valuesFrom, IGraph recipeGraph, Map<ICompoundContainer<?>, IContainerNode> compoundNodes) {
IResultsOwningNode node = constructContainerNode(valueWrapper, recipeGraph, compoundNodes);
node.results().base(CompoundInstanceSet.of(CompoundInformationRegistry.getInstance(primaryOwner.getIdentifier()).getBaseInformation().get(valuesFrom)));
return node;
}

private INode forceContainerNodeValues(ICompoundContainer<?> valueWrapper, ICompoundContainer<?> valuesFrom, IGraph recipeGraph, Map<ICompoundContainer<?>, IContainerNode> compoundNodes) {
IResultsOwningNode node = constructContainerNode(valueWrapper, recipeGraph, compoundNodes);
node.results().force(CompoundInstanceSet.of(CompoundInformationRegistry.getInstance(primaryOwner.getIdentifier()).getValueInformation().get(valuesFrom )));
return node;
}

private void handleRecipeInput(IRecipeIngredient input, Map<IRecipeIngredient, IIngredientNode> ingredientNodes, IGraph recipeGraph, RecipeNode recipeGraphNode, Map<ICompoundContainer<?>, IContainerNode> compoundNodes, int factor) {
final IRecipeIngredient unitIngredient = new SimpleIngredientBuilder().from(input).withCount(1).createIngredient();
ingredientNodes.putIfAbsent(unitIngredient, new IngredientNode(unitIngredient));
Expand Down Expand Up @@ -220,7 +239,7 @@ private void handleRecipeInput(IRecipeIngredient input, Map<IRecipeIngredient, I
return node;
}

private IGraph reduceGraph(final IGraph recipeGraph, final SourceNode sourceNode) {
private IGraph reduceGraph(final IGraph recipeGraph, final SourceNode sourceNode, Map<ICompoundContainer<?>, IContainerNode> compoundNodes) {

LOGGER.warn("Starting component detection");

Expand All @@ -247,6 +266,28 @@ private IGraph reduceGraph(final IGraph recipeGraph, final SourceNode sourceNode

LOGGER.warn("Finished clique reduction.");

LOGGER.warn("Stripping input values from known nodes.");

for (ICompoundContainer<?> valueWrapper : CompoundInformationRegistry.getInstance(primaryOwner.getIdentifier()).getValueInformation().keySet()) {
final Set<ICompoundContainer<?>> alternatives = ResultsAdapterHandlerRegistry.getInstance().produceAlternatives(valueWrapper.getContents());

constructContainerNode(valueWrapper, recipeGraph, compoundNodes).clearInputs();
for (ICompoundContainer<?> alternative : alternatives) {
constructContainerNode(alternative, recipeGraph, compoundNodes).clearInputs();
}
}

for (ICompoundContainer<?> valueWrapper : CompoundInformationRegistry.getInstance(primaryOwner.getIdentifier()).getBaseInformation().keySet()) {
final Set<ICompoundContainer<?>> alternatives = ResultsAdapterHandlerRegistry.getInstance().produceAlternatives(valueWrapper.getContents());

constructContainerNode(valueWrapper, recipeGraph, compoundNodes).clearInputs();
for (ICompoundContainer<?> alternative : alternatives) {
constructContainerNode(alternative, recipeGraph, compoundNodes).clearInputs();
}
}

LOGGER.warn("Stripped input values from known nodes.");

LOGGER.warn("Starting cycle reduction.");

final IJGraphTBasedCompoundCycleTracer tracer = createTracer();
Expand All @@ -262,17 +303,6 @@ private IGraph reduceGraph(final IGraph recipeGraph, final SourceNode sourceNode

LOGGER.warn("Finished cycle reduction.");

recipeGraph.removeVertex(sourceNode);

final Set<INode> sourceNodeLinks = findDanglingNodes(recipeGraph);

recipeGraph.addVertex(sourceNode);

for (INode rootNode : sourceNodeLinks) {
recipeGraph.addEdge(sourceNode, rootNode);
recipeGraph.setEdgeWeight(sourceNode, rootNode, 1d);
}

return recipeGraph;
}

Expand All @@ -282,14 +312,15 @@ public void calculate() {
}

final BuildRecipeGraph buildRecipeGraph = createGraph();
final Map<ICompoundContainer<?>, IContainerNode> compoundNodes = buildRecipeGraph.compoundNodes();
final IGraph noneReducedGraph = buildRecipeGraph.recipeGraph();
final Map<ICompoundContainer<?>, Set<CompoundInstance>> resultingCompounds = buildRecipeGraph.resultingCompounds();
final Set<INode> notDefinedGraphNodes = buildRecipeGraph.notDefinedGraphNodes();
final SourceNode source = buildRecipeGraph.sourceNode();

final String graphHash = new CacheKey(ModList.get()).hash();
LOGGER.warn("Starting graph analysis for: {}, with hash: {}", WorldUtils.formatWorldNames(getOwners()), graphHash);
if (!forceReload) {
if (!forceReload && Aequivaleo.getInstance().getConfiguration().getServer().useCaching.get()) {
//We are allowed to lookup cached values
final Optional<Map<ICompoundContainer<?>, Set<CompoundInstance>>> cachedResults = WorldCacheUtils.loadCachedResults(primaryOwner, graphHash);
if (cachedResults.isPresent()) {
Expand All @@ -302,7 +333,7 @@ public void calculate() {
LOGGER.info("Forcing reload of results for: {}", WorldUtils.formatWorldNames(getOwners()));
}

final IGraph recipeGraph = reduceGraph(noneReducedGraph, source);
final IGraph recipeGraph = reduceGraph(noneReducedGraph, source, compoundNodes);

final StatCollector statCollector = new StatCollector(
WorldUtils.formatWorldNames(getOwners()),
Expand All @@ -318,6 +349,11 @@ public void calculate() {
resultingCompounds.compute(valueWrapper, (w, v) -> Objects.requireNonNull(CompoundInformationRegistry.getInstance(primaryOwner.getIdentifier())
.getLockingInformation()
.get(valueWrapper)));

ResultsAdapterHandlerRegistry.getInstance().produceAlternatives(valueWrapper.getContents())
.forEach(alternative -> resultingCompounds.compute(alternative, (w, v) -> Objects.requireNonNull(CompoundInformationRegistry.getInstance(primaryOwner.getIdentifier())
.getLockingInformation()
.get(valueWrapper))));
}

if (Aequivaleo.getInstance().getConfiguration().getServer().writeResultsToLog.get()) {
Expand Down Expand Up @@ -346,7 +382,7 @@ public void calculate() {
AequivaleoLogger.bigWarningSimple(String.format("Finished the analysis of: %s", WorldUtils.formatWorldNames(getOwners())));
}

if (writeCachedData) {
if (writeCachedData && Aequivaleo.getInstance().getConfiguration().getServer().useCaching.get()) {
LOGGER.warn(String.format("Writing results to cache for: %s", WorldUtils.formatWorldNames(getOwners())));
WorldCacheUtils.writeCachedResults(primaryOwner, graphHash, resultingCompounds);
LOGGER.warn(String.format("Written %d results to cache for: %s", resultingCompounds.size(), WorldUtils.formatWorldNames(getOwners())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ public interface IInnerNode extends ISimulateableNode
@Override
default boolean canPropagate() {
final Iterator<ICoreNode> flatten = flatten();
while (flatten.hasNext()) {
if (flatten.next() instanceof IResultsOwningNode resultsOwningNode && resultsOwningNode.canPropagate()) {
return true;
}
if (flatten.hasNext()) {
do {
final INode next = flatten.next();
if (next instanceof IResultsOwningNode resultsOwningNode && resultsOwningNode.canPropagate()) {
return true;
}
} while (flatten.hasNext());
}

return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ default ISimulationManager simulationManager() {

@Override
default boolean canPropagate() {
return results().hasResults() || !requiresCalculation();
return results().hasResults() || !requiresCalculation() || results().canCalculate();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

public abstract class ResultsOwningNode extends CoreNode implements IResultsOwningNode {

private final IResultsContainer results = new SimulateableResultsContainer(this);
private final IResultsContainer results = new SimulateableResultsContainer();

@Override
public IResultsContainer results() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void analyze(IAnalysisState state) {
))
.collect(Collectors.toSet());

final IResultsContainer container = new SimulateableResultsContainer(this);
final IResultsContainer container = new SimulateableResultsContainer();
candidates.forEach(container::offer);
final CompoundInstanceSet completeSimulation = simulationState.doWhen(
container::simulate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ private Iterator<INode> createIterationOrder(INode node) {

return new Iterator<>() {

private final int startIndex = indexOfNodeInNodes(node);
private int currentIndex = (startIndex + 1) % nodes().length;
private int remaining = nodes().length - 1;
private int currentIndex = (indexOfNodeInNodes(node) + 1) % nodes().length;

@Override
public boolean hasNext() {
return currentIndex != startIndex;
return remaining > 0;
}

@Override
Expand All @@ -115,6 +115,7 @@ public INode next() {

INode nextNode = nodes()[currentIndex];
currentIndex = (currentIndex + 1) % nodes().length;
remaining--;
return nextNode;
}
};
Expand Down
Loading

0 comments on commit 392cac0

Please sign in to comment.