Skip to content

Commit

Permalink
feat: ability to retrieve the maximum amount craftable
Browse files Browse the repository at this point in the history
  • Loading branch information
raoulvdberge committed Dec 27, 2024
1 parent 7320e07 commit d0a11f2
Show file tree
Hide file tree
Showing 32 changed files with 465 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Added

- Autocrafting engine.
- The crafting preview now has the ability to fill out the maximum amount of a resource you can currently craft.

### Changed

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

class CalculationException extends RuntimeException {
protected CalculationException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

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);

long getMaxAmount(ResourceKey resource);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class CraftingCalculatorImpl implements CraftingCalculator {
private static final Logger LOGGER = LoggerFactory.getLogger(CraftingCalculatorImpl.class);

private final PatternRepository patternRepository;
private final RootStorage rootStorage;

Expand Down Expand Up @@ -48,4 +53,47 @@ public <T> void calculate(final ResourceKey resource,
}
listener.childCalculationCompleted(resource, lastPatternAmount.getTotal(), lastChildListener);
}

private boolean isCraftable(final ResourceKey resource, final long amount) {
final MissingResourcesCraftingCalculatorListener listener = new MissingResourcesCraftingCalculatorListener();
calculate(resource, amount, listener);
return !listener.isMissingResources();
}

@Override
public long getMaxAmount(final ResourceKey resource) {
try {
LOGGER.debug("Finding max amount for {} starting from 1", resource);
long low = 1;
long high = 1;
int calculationCount = 1;
while (isCraftable(resource, high)) {
low = high;
high = high * 2;
LOGGER.debug("Finding low and high for the craftable amount, currently between {} and {}", low, high);
calculationCount++;
}
if (low == high) {
return 0;
}
LOGGER.debug("Our craftable amount is between {} and {}", low, high);
while (low < high) {
final long amount = low + (high - low + 1) / 2;
LOGGER.debug("Trying {} (between {} and {})", amount, low, high);
calculationCount++;
if (isCraftable(resource, amount)) {
LOGGER.debug("{} was craftable, increasing our low amount", amount);
low = amount;
} else {
LOGGER.debug("{} is not craftable, decreasing our high amount", amount);
high = amount - 1;
}
}
LOGGER.debug("Found the maximum amount of {} in {} tries", low, calculationCount);
return low;
} catch (final CalculationException e) {
LOGGER.debug("Failed to calculate the maximum amount", e);
return 0;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.refinedmods.refinedstorage.api.autocrafting.calculation;

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

class MissingResourcesCraftingCalculatorListener implements CraftingCalculatorListener<Boolean> {
private boolean missingResources;

MissingResourcesCraftingCalculatorListener() {
}

MissingResourcesCraftingCalculatorListener(final boolean missingResources) {
this.missingResources = missingResources;
}

boolean isMissingResources() {
return missingResources;
}

@Override
public CraftingCalculatorListener<Boolean> childCalculationStarted() {
return new MissingResourcesCraftingCalculatorListener(missingResources);
}

@Override
public void childCalculationCompleted(final ResourceKey resource,
final long amount,
final CraftingCalculatorListener<Boolean> childListener) {
missingResources = ((MissingResourcesCraftingCalculatorListener) childListener).missingResources;
}

@Override
public void ingredientsExhausted(final ResourceKey resource, final long amount) {
missingResources = true;
}

@Override
public void ingredientExtractedFromStorage(final ResourceKey resource, final long amount) {
// no op
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.refinedmods.refinedstorage.api.autocrafting.calculation;

public class NumberOverflowDuringCalculationException extends RuntimeException {
public class NumberOverflowDuringCalculationException extends CalculationException {
NumberOverflowDuringCalculationException() {
super("Invalid amount");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.refinedmods.refinedstorage.api.autocrafting.Pattern;

public class PatternCycleDetectedException extends RuntimeException {
public class PatternCycleDetectedException extends CalculationException {
private final Pattern pattern;

PatternCycleDetectedException(final Pattern pattern) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@
public interface PreviewProvider {
Optional<Preview> getPreview(ResourceKey resource, long amount);

long getMaxAmount(ResourceKey resource);

boolean startTask(ResourceKey resource, long amount);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.refinedmods.refinedstorage.api.autocrafting;

import com.refinedmods.refinedstorage.api.core.Action;
import com.refinedmods.refinedstorage.api.resource.ResourceAmount;
import com.refinedmods.refinedstorage.api.storage.EmptyActor;
import com.refinedmods.refinedstorage.api.storage.StorageImpl;
import com.refinedmods.refinedstorage.api.storage.root.RootStorage;
import com.refinedmods.refinedstorage.api.storage.root.RootStorageImpl;

public final class AutocraftingHelpers {
private AutocraftingHelpers() {
}

public static RootStorage storage(final ResourceAmount... resourceAmounts) {
final RootStorage storage = new RootStorageImpl();
storage.addSource(new StorageImpl());
for (final ResourceAmount resourceAmount : resourceAmounts) {
storage.insert(resourceAmount.resource(), resourceAmount.amount(), Action.EXECUTE, EmptyActor.INSTANCE);
}
return storage;
}

public static PatternRepository patterns(final Pattern... patterns) {
final PatternRepository patternRepository = new PatternRepositoryImpl();
for (final Pattern pattern : patterns) {
patternRepository.add(pattern);
}
return patternRepository;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.refinedmods.refinedstorage.api.autocrafting.calculation;

import com.refinedmods.refinedstorage.api.autocrafting.PatternRepository;
import com.refinedmods.refinedstorage.api.resource.ResourceAmount;
import com.refinedmods.refinedstorage.api.storage.root.RootStorage;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static com.refinedmods.refinedstorage.api.autocrafting.AutocraftingHelpers.patterns;
import static com.refinedmods.refinedstorage.api.autocrafting.AutocraftingHelpers.storage;
import static com.refinedmods.refinedstorage.api.autocrafting.PatternBuilder.pattern;
import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.CRAFTING_TABLE;
import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.OAK_LOG;
import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.OAK_PLANKS;
import static org.assertj.core.api.Assertions.assertThat;

class CraftingCalculatorImplTest {
@Test
void shouldNotFindMaxAmountIfThereAreAlwaysMissingResources() {
// Arrange
final RootStorage storage = storage(
new ResourceAmount(OAK_PLANKS, 1)
);
final PatternRepository patterns = patterns(
pattern()
.ingredient(OAK_LOG, 1)
.output(OAK_PLANKS, 4)
.build(),
pattern()
.ingredient(OAK_PLANKS, 4)
.output(CRAFTING_TABLE, 1)
.build()
);
final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage);

// Act
final long maxAmount = sut.getMaxAmount(CRAFTING_TABLE);

// Assert
assertThat(maxAmount).isZero();
}

@ParameterizedTest
@ValueSource(longs = {1L, 2L, 3L, 4L, 5L, 6L, 7L, 64L, 128L})
void shouldFindMaxAmount(final long amountPossible) {
// Arrange
final RootStorage storage = storage(
new ResourceAmount(OAK_LOG, amountPossible)
);
final PatternRepository patterns = patterns(
pattern()
.ingredient(OAK_LOG, 1)
.output(OAK_PLANKS, 4)
.build(),
pattern()
.ingredient(OAK_PLANKS, 4)
.output(CRAFTING_TABLE, 1)
.build()
);
final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage);

// Act
final long maxAmount = sut.getMaxAmount(CRAFTING_TABLE);

// Assert
assertThat(maxAmount).isEqualTo(amountPossible);
}

@Test
void shouldNotFindMaxAmountIfThereIsANumberOverflow() {
// Arrange
final RootStorage storage = storage(
new ResourceAmount(OAK_PLANKS, Long.MAX_VALUE)
);
final PatternRepository patterns = patterns(
pattern()
.ingredient(OAK_LOG, 1)
.output(OAK_PLANKS, 4)
.build(),
pattern()
.ingredient(OAK_PLANKS, 4)
.output(CRAFTING_TABLE, 1)
.build()
);
final CraftingCalculator sut = new CraftingCalculatorImpl(patterns, storage);

// Act
final long maxAmount = sut.getMaxAmount(CRAFTING_TABLE);

// Assert
assertThat(maxAmount).isZero();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@FieldsAndMethodsAreNonnullByDefault
package com.refinedmods.refinedstorage.api.autocrafting.calculation;

import com.refinedmods.refinedstorage.api.core.FieldsAndMethodsAreNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@

import com.refinedmods.refinedstorage.api.autocrafting.Pattern;
import com.refinedmods.refinedstorage.api.autocrafting.PatternRepository;
import com.refinedmods.refinedstorage.api.autocrafting.PatternRepositoryImpl;
import com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingCalculator;
import com.refinedmods.refinedstorage.api.autocrafting.calculation.CraftingCalculatorImpl;
import com.refinedmods.refinedstorage.api.core.Action;
import com.refinedmods.refinedstorage.api.resource.ResourceAmount;
import com.refinedmods.refinedstorage.api.storage.EmptyActor;
import com.refinedmods.refinedstorage.api.storage.StorageImpl;
import com.refinedmods.refinedstorage.api.storage.root.RootStorage;
import com.refinedmods.refinedstorage.api.storage.root.RootStorageImpl;

import java.util.stream.Stream;

Expand All @@ -22,6 +17,8 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

import static com.refinedmods.refinedstorage.api.autocrafting.AutocraftingHelpers.patterns;
import static com.refinedmods.refinedstorage.api.autocrafting.AutocraftingHelpers.storage;
import static com.refinedmods.refinedstorage.api.autocrafting.PatternBuilder.pattern;
import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.CRAFTING_TABLE;
import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.OAK_LOG;
Expand Down Expand Up @@ -653,22 +650,4 @@ void shouldDetectNumberOverflowInChildPattern() {
// Assert
assertThat(preview).usingRecursiveComparison().isEqualTo(PreviewBuilder.ofType(OVERFLOW).build());
}


private static RootStorage storage(final ResourceAmount... resourceAmounts) {
final RootStorage storage = new RootStorageImpl();
storage.addSource(new StorageImpl());
for (final ResourceAmount resourceAmount : resourceAmounts) {
storage.insert(resourceAmount.resource(), resourceAmount.amount(), Action.EXECUTE, EmptyActor.INSTANCE);
}
return storage;
}

private static PatternRepository patterns(final Pattern... patterns) {
final PatternRepository patternRepository = new PatternRepositoryImpl();
for (final Pattern pattern : patterns) {
patternRepository.add(pattern);
}
return patternRepository;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import com.refinedmods.refinedstorage.api.autocrafting.preview.Preview;
import com.refinedmods.refinedstorage.api.resource.ResourceAmount;
import com.refinedmods.refinedstorage.common.api.support.resource.PlatformResourceKey;
import com.refinedmods.refinedstorage.common.api.support.resource.ResourceContainer;
import com.refinedmods.refinedstorage.common.support.containermenu.AbstractResourceContainerMenu;
import com.refinedmods.refinedstorage.common.support.containermenu.DisabledResourceSlot;
import com.refinedmods.refinedstorage.common.support.containermenu.ResourceSlotType;
import com.refinedmods.refinedstorage.common.support.packet.c2s.C2SPackets;
import com.refinedmods.refinedstorage.common.support.resource.ResourceContainerImpl;

import java.util.ArrayList;
Expand Down Expand Up @@ -104,4 +106,19 @@ public void responseReceived(final UUID id, final boolean started) {
setCurrentRequest(requests.getFirst());
}
}

public void maxAmountResponseReceived(final long maxAmount) {
if (listener == null) {
return;
}
if (currentRequest.getResource() instanceof PlatformResourceKey resource) {
listener.maxAmountReceived(resource.getResourceType().getDisplayAmount(maxAmount));
}
}

void requestMaxAmount() {
if (currentRequest.getResource() instanceof PlatformResourceKey resource) {
C2SPackets.sendAutocraftingPreviewMaxAmountRequest(resource);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ interface AutocraftingPreviewListener {
void previewChanged(@Nullable Preview preview);

void requestRemoved(AutocraftingRequest request, boolean last);

void maxAmountReceived(double maxAmount);
}
Loading

0 comments on commit d0a11f2

Please sign in to comment.