Skip to content

Commit

Permalink
Merge pull request #762 from refinedmods/feat/GH-612/autocrafting-thr…
Browse files Browse the repository at this point in the history
…eading

Autocrafting threading
  • Loading branch information
raoulvdberge authored Jan 1, 2025
2 parents 7ceb7e6 + c627923 commit 3337422
Show file tree
Hide file tree
Showing 152 changed files with 2,212 additions and 2,013 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

- Autocrafting engine.
- The crafting preview now has the ability to fill out the maximum amount of a resource you can currently craft.
- In the crafting preview, you can now indicate whether you want to be notified when an autocrafting task is finished.

### Changed

Expand Down
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The MIT License (MIT)

Copyright © 2020 - 2024 Refined Mods
Copyright © 2020 - 2025 Refined Mods

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,15 @@
package com.refinedmods.refinedstorage.api.autocrafting;

import com.refinedmods.refinedstorage.api.core.CoreValidations;
import com.refinedmods.refinedstorage.api.resource.ResourceKey;

import java.util.Collections;
import java.util.List;

public class Ingredient {
private final long amount;
private final List<ResourceKey> inputs;

public Ingredient(final long amount, final List<? extends ResourceKey> inputs) {
public record Ingredient(long amount, List<ResourceKey> inputs) {
public Ingredient(final long amount, final List<ResourceKey> inputs) {
CoreValidations.validateLargerThanZero(amount, "Amount must be larger than zero");
CoreValidations.validateNotEmpty(inputs, "Inputs cannot be empty");
this.amount = amount;
this.inputs = Collections.unmodifiableList(inputs);
}

public boolean isEmpty() {
return inputs.isEmpty();
}

public int size() {
return inputs.size();
}

public long getAmount() {
return amount;
}

public ResourceKey get(final int index) {
return inputs.get(index);
}

@Override
public String toString() {
return "Ingredient{"
+ "amount=" + amount
+ ", inputs=" + inputs
+ '}';
this.inputs = List.copyOf(inputs);
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package com.refinedmods.refinedstorage.api.autocrafting;

import com.refinedmods.refinedstorage.api.core.CoreValidations;
import com.refinedmods.refinedstorage.api.resource.ResourceAmount;
import com.refinedmods.refinedstorage.api.resource.ResourceKey;

import java.util.List;
import java.util.Set;

import org.apiguardian.api.API;

@API(status = API.Status.STABLE, since = "2.0.0-milestone.4.6")
public interface Pattern {
Set<ResourceKey> getInputResources();

Set<ResourceKey> getOutputResources();

List<Ingredient> getIngredients();

List<ResourceAmount> getOutputs();
public record Pattern(List<Ingredient> ingredients, List<ResourceAmount> outputs) {
public Pattern(final List<Ingredient> ingredients, final List<ResourceAmount> outputs) {
CoreValidations.validateNotEmpty(ingredients, "Ingredients cannot be empty");
CoreValidations.validateNotEmpty(outputs, "Outputs cannot be empty");
this.ingredients = List.copyOf(ingredients);
this.outputs = List.copyOf(outputs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public IngredientBuilder ingredient(final long amount) {
return new IngredientBuilder(amount);
}

public PatternBuilder ingredient(final Ingredient ingredient) {
ingredients.add(ingredient);
return this;
}

public PatternBuilder ingredient(final ResourceKey input, final long amount) {
ingredients.add(new Ingredient(amount, List.of(input)));
return this;
Expand All @@ -32,7 +37,7 @@ public PatternBuilder output(final ResourceKey output, final long amount) {
}

public Pattern build() {
return new PatternImpl(ingredients, outputs.toArray(new ResourceAmount[0]));
return new Pattern(ingredients, outputs);
}

public class IngredientBuilder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@

import com.refinedmods.refinedstorage.api.resource.ResourceKey;

import java.util.List;
import java.util.Collection;
import java.util.Set;

import org.apiguardian.api.API;

@API(status = API.Status.STABLE, since = "2.0.0-milestone.4.8")
public interface PatternRepository {
void add(Pattern pattern);
void add(Pattern pattern, int priority);

void update(Pattern pattern, int priority);

void remove(Pattern pattern);

List<Pattern> getByOutput(ResourceKey output);
Collection<Pattern> getByOutput(ResourceKey output);

Set<ResourceKey> getOutputs();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,87 @@
package com.refinedmods.refinedstorage.api.autocrafting;

import com.refinedmods.refinedstorage.api.resource.ResourceAmount;
import com.refinedmods.refinedstorage.api.resource.ResourceKey;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;

public class PatternRepositoryImpl implements PatternRepository {
private final Set<Pattern> patterns = new HashSet<>();
private final Map<ResourceKey, List<Pattern>> patternsByOutput = new HashMap<>();
private final Set<Pattern> patternsView = Collections.unmodifiableSet(patterns);
private final Map<ResourceKey, PriorityQueue<PatternHolder>> patternsByOutput = new HashMap<>();
private final Set<ResourceKey> outputs = new HashSet<>();
private final Set<ResourceKey> outputsView = Collections.unmodifiableSet(outputs);

@Override
public void add(final Pattern pattern) {
public void add(final Pattern pattern, final int priority) {
patterns.add(pattern);
outputs.addAll(pattern.getOutputResources());
for (final ResourceKey output : pattern.getOutputResources()) {
patternsByOutput.computeIfAbsent(output, k -> new ArrayList<>()).add(pattern);
pattern.outputs().forEach(output -> outputs.add(output.resource()));
for (final ResourceAmount output : pattern.outputs()) {
patternsByOutput.computeIfAbsent(output.resource(), k -> new PriorityQueue<>(
Comparator.comparingInt(PatternHolder::priority).reversed()
)).add(new PatternHolder(pattern, priority));
}
}

@Override
public void update(final Pattern pattern, final int priority) {
for (final ResourceAmount output : pattern.outputs()) {
final PriorityQueue<PatternHolder> holders = patternsByOutput.get(output.resource());
if (holders == null) {
continue;
}
holders.removeIf(holder -> holder.pattern.equals(pattern));
holders.add(new PatternHolder(pattern, priority));
}
}

@Override
public void remove(final Pattern pattern) {
patterns.remove(pattern);
for (final ResourceKey output : pattern.getOutputResources()) {
final List<Pattern> byOutput = patternsByOutput.get(output);
if (byOutput == null) {
for (final ResourceAmount output : pattern.outputs()) {
final PriorityQueue<PatternHolder> holders = patternsByOutput.get(output.resource());
if (holders == null) {
continue;
}
byOutput.remove(pattern);
if (byOutput.isEmpty()) {
patternsByOutput.remove(output);
holders.removeIf(holder -> holder.pattern.equals(pattern));
if (holders.isEmpty()) {
patternsByOutput.remove(output.resource());
}
final boolean noOtherPatternHasThisOutput = patterns.stream()
.noneMatch(otherPattern -> otherPattern.getOutputResources().contains(output));
.noneMatch(otherPattern -> otherPattern.outputs().stream()
.anyMatch(o -> o.resource().equals(output.resource())));
if (noOtherPatternHasThisOutput) {
outputs.remove(output);
outputs.remove(output.resource());
}
}
}

@Override
public List<Pattern> getByOutput(final ResourceKey output) {
return patternsByOutput.getOrDefault(output, Collections.emptyList());
final PriorityQueue<PatternHolder> holders = patternsByOutput.get(output);
if (holders == null) {
return Collections.emptyList();
}
return holders.stream().map(holder -> holder.pattern).toList();
}

@Override
public Set<ResourceKey> getOutputs() {
return outputs;
return outputsView;
}

@Override
public Set<Pattern> getAll() {
return patternsView;
}

private record PatternHolder(Pattern pattern, int priority) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public long getTotal() {
}

static Amount of(final Pattern pattern, final ResourceKey resource, final long requestedAmount) {
final long amountPerIteration = pattern.getOutputs()
final long amountPerIteration = pattern.outputs()
.stream()
.filter(output -> output.resource().equals(resource))
.mapToLong(ResourceAmount::amount)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import com.refinedmods.refinedstorage.api.resource.ResourceKey;
import com.refinedmods.refinedstorage.api.storage.root.RootStorage;

import java.util.List;
import java.util.Collection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -29,29 +29,30 @@ public <T> void calculate(final ResourceKey resource,
final long amount,
final CraftingCalculatorListener<T> listener) {
CoreValidations.validateLargerThanZero(amount, "Requested amount must be greater than 0");
final List<Pattern> patterns = patternRepository.getByOutput(resource);
final Collection<Pattern> patterns = patternRepository.getByOutput(resource);
CraftingCalculatorListener<T> lastChildListener = null;
Amount lastPatternAmount = null;
for (final Pattern pattern : patterns) {
final Amount patternAmount = Amount.of(pattern, resource, amount);
if (patternAmount.getTotal() < 0) {
throw new NumberOverflowDuringCalculationException();
}
final CraftingCalculatorListener<T> childListener = listener.childCalculationStarted();
final CraftingCalculatorListener<T> childListener = listener.childCalculationStarted(
resource,
patternAmount.getTotal()
);
final CraftingTree<T> tree = root(pattern, rootStorage, patternAmount, patternRepository, childListener);
final CraftingTree.CalculationResult calculationResult = tree.calculate();
if (calculationResult == CraftingTree.CalculationResult.MISSING_RESOURCES) {
lastChildListener = childListener;
lastPatternAmount = patternAmount;
continue;
}
listener.childCalculationCompleted(resource, patternAmount.getTotal(), childListener);
listener.childCalculationCompleted(childListener);
return;
}
if (lastChildListener == null) {
throw new IllegalStateException("No pattern found for " + resource);
}
listener.childCalculationCompleted(resource, lastPatternAmount.getTotal(), lastChildListener);
listener.childCalculationCompleted(lastChildListener);
}

private boolean isCraftable(final ResourceKey resource, final long amount) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

@API(status = API.Status.STABLE, since = "2.0.0-milestone.4.12")
public interface CraftingCalculatorListener<T> {
CraftingCalculatorListener<T> childCalculationStarted();
CraftingCalculatorListener<T> childCalculationStarted(ResourceKey resource, long amount);

void childCalculationCompleted(ResourceKey resource, long amount, CraftingCalculatorListener<T> childListener);
void childCalculationCompleted(CraftingCalculatorListener<T> childListener);

void ingredientsExhausted(ResourceKey resource, long amount);

void ingredientExtractedFromStorage(ResourceKey resource, long amount);

T getData();
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ void extractFromStorage(final ResourceKey resource, final long amount) {
}

void addOutputsToInternalStorage(final Pattern pattern, final Amount amount) {
pattern.getOutputs().forEach(
output -> addOutputToInternalStorage(amount, output)
);
pattern.outputs().forEach(output -> addOutputToInternalStorage(amount, output));
}

private void addOutputToInternalStorage(final Amount amount, final ResourceAmount output) {
Expand Down
Loading

0 comments on commit 3337422

Please sign in to comment.