Skip to content

Commit

Permalink
Merge pull request #755 from refinedmods/feat/GH-612/engine-start
Browse files Browse the repository at this point in the history
feat: autocrafting calculation
  • Loading branch information
raoulvdberge authored Dec 25, 2024
2 parents 04984b1 + 3aef767 commit 36bbd76
Show file tree
Hide file tree
Showing 43 changed files with 1,843 additions and 178 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Autocrafting engine.

### Changed

- Autocrafting now handles multiple patterns with the same output correctly by trying to use the pattern with the
highest priority first. If there are missing resources, lower priority patterns are checked.

## [2.0.0-milestone.4.11] - 2024-12-08

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.refinedmods.refinedstorage.api.autocrafting;

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) {
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
+ '}';
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.refinedmods.refinedstorage.api.autocrafting;

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;
Expand All @@ -11,4 +13,8 @@ public interface Pattern {
Set<ResourceKey> getInputResources();

Set<ResourceKey> getOutputResources();

List<Ingredient> getIngredients();

List<ResourceAmount> getOutputs();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

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

import org.apiguardian.api.API;
Expand All @@ -12,6 +13,8 @@ public interface PatternRepository {

void remove(Pattern pattern);

List<Pattern> getByOutput(ResourceKey output);

Set<ResourceKey> getOutputs();

Set<Pattern> getAll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,41 @@

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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 Set<ResourceKey> outputs = new HashSet<>();

@Override
public void add(final Pattern pattern) {
patterns.add(pattern);
outputs.addAll(pattern.getOutputResources());
for (final ResourceKey output : pattern.getOutputResources()) {
patternsByOutput.computeIfAbsent(output, k -> new ArrayList<>()).add(pattern);
}
}

@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) {
continue;
}
byOutput.remove(pattern);
if (byOutput.isEmpty()) {
patternsByOutput.remove(output);
}
final boolean noOtherPatternHasThisOutput = patterns.stream()
.noneMatch(otherPattern -> otherPattern.getOutputResources().contains(output));
if (noOtherPatternHasThisOutput) {
Expand All @@ -29,6 +45,11 @@ public void remove(final Pattern pattern) {
}
}

@Override
public List<Pattern> getByOutput(final ResourceKey output) {
return patternsByOutput.getOrDefault(output, Collections.emptyList());
}

@Override
public Set<ResourceKey> getOutputs() {
return outputs;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.refinedmods.refinedstorage.api.autocrafting.calculation;

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

record Amount(long iterations, long amountPerIteration) {
public long getTotal() {
return iterations * amountPerIteration;
}

static Amount of(final Pattern pattern, final ResourceKey resource, final long requestedAmount) {
final long amountPerIteration = pattern.getOutputs()
.stream()
.filter(output -> output.resource().equals(resource))
.mapToLong(ResourceAmount::amount)
.sum();
final long iterations = ((requestedAmount - 1) / amountPerIteration) + 1;
return new Amount(iterations, amountPerIteration);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.refinedmods.refinedstorage.api.autocrafting.calculation;

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

import org.apiguardian.api.API;

@FunctionalInterface
@API(status = API.Status.STABLE, since = "2.0.0-milestone.4.12")
public interface CraftingCalculator {
<T> void calculate(ResourceKey resource, long amount, CraftingCalculatorListener<T> listener);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.refinedmods.refinedstorage.api.autocrafting.calculation;

import com.refinedmods.refinedstorage.api.autocrafting.Pattern;
import com.refinedmods.refinedstorage.api.autocrafting.PatternRepository;
import com.refinedmods.refinedstorage.api.core.CoreValidations;
import com.refinedmods.refinedstorage.api.resource.ResourceKey;
import com.refinedmods.refinedstorage.api.storage.root.RootStorage;

import java.util.List;

import static com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingTree.root;

public class CraftingCalculatorImpl implements CraftingCalculator {
private final PatternRepository patternRepository;
private final RootStorage rootStorage;

public CraftingCalculatorImpl(final PatternRepository patternRepository, final RootStorage rootStorage) {
this.patternRepository = patternRepository;
this.rootStorage = rootStorage;
}

@Override
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);
CraftingCalculatorListener<T> lastChildListener = null;
Amount lastPatternAmount = null;
for (final Pattern pattern : patterns) {
final Amount patternAmount = Amount.of(pattern, resource, amount);
final CraftingCalculatorListener<T> childListener = listener.childCalculationStarted();
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);
return;
}
if (lastChildListener == null) {
throw new IllegalStateException("No pattern found for " + resource);
}
listener.childCalculationCompleted(resource, lastPatternAmount.getTotal(), lastChildListener);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.refinedmods.refinedstorage.api.autocrafting.calculation;

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

import org.apiguardian.api.API;

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

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

void ingredientsExhausted(ResourceKey resource, long amount);

void ingredientExtractedFromStorage(ResourceKey resource, long amount);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.refinedmods.refinedstorage.api.autocrafting.calculation;

import com.refinedmods.refinedstorage.api.autocrafting.Pattern;
import com.refinedmods.refinedstorage.api.resource.ResourceKey;
import com.refinedmods.refinedstorage.api.resource.list.MutableResourceList;
import com.refinedmods.refinedstorage.api.resource.list.MutableResourceListImpl;
import com.refinedmods.refinedstorage.api.storage.root.RootStorage;

import java.util.Comparator;

class CraftingState {
private final MutableResourceList storage;
private final MutableResourceList internalStorage;

private CraftingState(final MutableResourceList storage,
final MutableResourceList internalStorage) {
this.storage = storage;
this.internalStorage = internalStorage;
}

void extractFromInternalStorage(final ResourceKey resource, final long amount) {
internalStorage.remove(resource, amount);
}

void extractFromStorage(final ResourceKey resource, final long amount) {
storage.remove(resource, amount);
}

void addOutputsToInternalStorage(final Pattern pattern, final Amount amount) {
pattern.getOutputs().forEach(
output -> internalStorage.add(output.resource(), output.amount() * amount.iterations())
);
}

CraftingState copy() {
return new CraftingState(storage.copy(), internalStorage.copy());
}

static CraftingState of(final RootStorage rootStorage) {
final MutableResourceListImpl storage = MutableResourceListImpl.create();
rootStorage.getAll().forEach(storage::add);
return new CraftingState(storage, MutableResourceListImpl.create());
}

Comparator<ResourceKey> storageSorter() {
return sorter(storage);
}

Comparator<ResourceKey> internalStorageSorter() {
return sorter(internalStorage);
}

private Comparator<ResourceKey> sorter(final MutableResourceList list) {
return (a, b) -> {
final long ar = list.get(a);
final long br = list.get(b);
return (int) br - (int) ar;
};
}

ResourceState getResource(final ResourceKey resource) {
return new ResourceState(resource, storage.get(resource), internalStorage.get(resource));
}

record ResourceState(ResourceKey resource, long inStorage, long inInternalStorage) {
boolean isInStorage() {
return inStorage > 0;
}

boolean isInInternalStorage() {
return inInternalStorage > 0;
}
}
}
Loading

0 comments on commit 36bbd76

Please sign in to comment.