diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/status/TaskStatus.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/status/TaskStatus.java index 4a22851c7..e0e633e16 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/status/TaskStatus.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/status/TaskStatus.java @@ -22,6 +22,7 @@ public record Item( ) { } + // TODO: crafter logic doesn't know what to do with these public enum ItemType { NORMAL, MACHINE_DOES_NOT_ACCEPT_RESOURCE, diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/AbstractTaskPattern.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/AbstractTaskPattern.java index eb7afafc5..34f417e6e 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/AbstractTaskPattern.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/AbstractTaskPattern.java @@ -33,9 +33,9 @@ protected AbstractTaskPattern(final Pattern pattern, final TaskPlan.PatternPlan } } - abstract boolean step(MutableResourceList internalStorage, - RootStorage rootStorage, - ExternalPatternInputSink externalPatternInputSink); + abstract PatternStepResult step(MutableResourceList internalStorage, + RootStorage rootStorage, + ExternalPatternInputSink externalPatternInputSink); abstract RootStorageListener.InterceptResult interceptInsertion(ResourceKey resource, long amount); diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/ExternalTaskPattern.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/ExternalTaskPattern.java index ad5c7c6fd..5201928ec 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/ExternalTaskPattern.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/ExternalTaskPattern.java @@ -22,6 +22,7 @@ class ExternalTaskPattern extends AbstractTaskPattern { private final long originalIterationsToSendToSink; private long iterationsToSendToSink; private long iterationsReceived; + private boolean interceptedAnythingSinceLastStep; ExternalTaskPattern(final Pattern pattern, final TaskPlan.PatternPlan plan) { super(pattern, plan); @@ -34,21 +35,25 @@ class ExternalTaskPattern extends AbstractTaskPattern { } @Override - boolean step(final MutableResourceList internalStorage, - final RootStorage rootStorage, - final ExternalPatternInputSink externalPatternInputSink) { + PatternStepResult step(final MutableResourceList internalStorage, + final RootStorage rootStorage, + final ExternalPatternInputSink externalPatternInputSink) { if (expectedOutputs.isEmpty()) { - return true; + return PatternStepResult.COMPLETED; + } + if (interceptedAnythingSinceLastStep) { + interceptedAnythingSinceLastStep = false; + return PatternStepResult.RUNNING; } if (iterationsToSendToSink == 0) { - return false; + return PatternStepResult.IDLE; } if (!acceptsIterationInputs(internalStorage, externalPatternInputSink)) { - return false; + return PatternStepResult.IDLE; } LOGGER.debug("Stepped {} with {} iterations remaining", pattern, iterationsToSendToSink); iterationsToSendToSink--; - return false; + return PatternStepResult.RUNNING; } @Override @@ -76,6 +81,7 @@ private void updateIterationsReceived() { } } this.iterationsReceived = result; + this.interceptedAnythingSinceLastStep = true; } @Override diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/InternalTaskPattern.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/InternalTaskPattern.java index 5c9b7f09c..2c37dc3ad 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/InternalTaskPattern.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/InternalTaskPattern.java @@ -27,12 +27,12 @@ class InternalTaskPattern extends AbstractTaskPattern { } @Override - boolean step(final MutableResourceList internalStorage, - final RootStorage rootStorage, - final ExternalPatternInputSink externalPatternInputSink) { + PatternStepResult step(final MutableResourceList internalStorage, + final RootStorage rootStorage, + final ExternalPatternInputSink externalPatternInputSink) { final ResourceList iterationInputsSimulated = calculateIterationInputs(Action.SIMULATE); if (!extractAll(iterationInputsSimulated, internalStorage, Action.SIMULATE)) { - return false; + return PatternStepResult.IDLE; } LOGGER.debug("Stepping {}", pattern); final ResourceList iterationInputs = calculateIterationInputs(Action.EXECUTE); @@ -84,9 +84,9 @@ long getWeight() { return iterationsCompleted / originalIterationsRemaining; } - protected boolean useIteration() { + protected PatternStepResult useIteration() { iterationsRemaining--; LOGGER.debug("Stepped {} with {} iterations remaining", pattern, iterationsRemaining); - return iterationsRemaining == 0; + return iterationsRemaining == 0 ? PatternStepResult.COMPLETED : PatternStepResult.RUNNING; } } diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/PatternStepResult.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/PatternStepResult.java new file mode 100644 index 000000000..aaf416e5a --- /dev/null +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/PatternStepResult.java @@ -0,0 +1,21 @@ +package com.refinedmods.refinedstorage.api.autocrafting.task; + +enum PatternStepResult { + COMPLETED, + RUNNING, + IDLE; + + PatternStepResult and(final PatternStepResult stepResult) { + if (this == IDLE) { + return stepResult; + } else if (this == COMPLETED) { + return COMPLETED; + } else { + return stepResult == COMPLETED ? COMPLETED : RUNNING; + } + } + + boolean isChanged() { + return this != IDLE; + } +} diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/Task.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/Task.java index e7fd26bc4..727b49902 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/Task.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/Task.java @@ -17,7 +17,7 @@ public interface Task extends RootStorageListener { Collection copyInternalStorageState(); - void step(RootStorage rootStorage, ExternalPatternInputSink externalPatternInputSink, StepBehavior stepBehavior); + boolean step(RootStorage rootStorage, ExternalPatternInputSink externalPatternInputSink, StepBehavior stepBehavior); void cancel(); diff --git a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/TaskImpl.java b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/TaskImpl.java index ca86a680b..883ef9501 100644 --- a/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/TaskImpl.java +++ b/refinedstorage-autocrafting-api/src/main/java/com/refinedmods/refinedstorage/api/autocrafting/task/TaskImpl.java @@ -77,15 +77,16 @@ private void updateState(final TaskState newState) { } @Override - public void step(final RootStorage rootStorage, - final ExternalPatternInputSink externalPatternInputSink, - final StepBehavior stepBehavior) { - switch (state) { + public boolean step(final RootStorage rootStorage, + final ExternalPatternInputSink externalPatternInputSink, + final StepBehavior stepBehavior) { + return switch (state) { case READY -> startTask(rootStorage); case EXTRACTING_INITIAL_RESOURCES -> extractInitialResourcesAndTryStartRunningTask(rootStorage); case RUNNING -> stepPatterns(rootStorage, externalPatternInputSink, stepBehavior); case RETURNING_INTERNAL_STORAGE -> returnInternalStorageAndTryCompleteTask(rootStorage); - } + case COMPLETED -> false; + }; } @Override @@ -112,23 +113,49 @@ public TaskStatus getStatus() { return builder.build(totalWeightedCompleted / totalWeight); } - private void startTask(final RootStorage rootStorage) { + private boolean startTask(final RootStorage rootStorage) { updateState(TaskState.EXTRACTING_INITIAL_RESOURCES); - extractInitialResourcesAndTryStartRunningTask(rootStorage); + return extractInitialResourcesAndTryStartRunningTask(rootStorage); } - private void extractInitialResourcesAndTryStartRunningTask(final RootStorage rootStorage) { - if (extractInitialResources(rootStorage)) { + private boolean extractInitialResourcesAndTryStartRunningTask(final RootStorage rootStorage) { + boolean extractedAll = true; + boolean extractedAny = false; + final Set initialRequirementResources = new HashSet<>(initialRequirements.getAll()); + for (final ResourceKey initialRequirementResource : initialRequirementResources) { + final long needed = initialRequirements.get(initialRequirementResource); + final long extracted = rootStorage.extract(initialRequirementResource, needed, Action.EXECUTE, Actor.EMPTY); + if (extracted > 0) { + extractedAny = true; + } + LOGGER.debug("Extracted {}x {} from storage", extracted, initialRequirementResource); + if (extracted != needed) { + extractedAll = false; + } + if (extracted > 0) { + initialRequirements.remove(initialRequirementResource, extracted); + internalStorage.add(initialRequirementResource, extracted); + } + } + if (extractedAll) { updateState(TaskState.RUNNING); } - } - - private void stepPatterns(final RootStorage rootStorage, - final ExternalPatternInputSink externalPatternInputSink, - final StepBehavior stepBehavior) { - patterns.entrySet().removeIf( - pattern -> stepPattern(rootStorage, externalPatternInputSink, stepBehavior, pattern) - ); + return extractedAny; + } + + private boolean stepPatterns(final RootStorage rootStorage, + final ExternalPatternInputSink externalPatternInputSink, + final StepBehavior stepBehavior) { + final var it = patterns.entrySet().iterator(); + boolean changed = false; + while (it.hasNext()) { + final var pattern = it.next(); + final PatternStepResult result = stepPattern(rootStorage, externalPatternInputSink, stepBehavior, pattern); + if (result == PatternStepResult.COMPLETED) { + it.remove(); + } + changed |= result.isChanged(); + } if (patterns.isEmpty()) { if (internalStorage.isEmpty()) { updateState(TaskState.COMPLETED); @@ -136,62 +163,45 @@ private void stepPatterns(final RootStorage rootStorage, updateState(TaskState.RETURNING_INTERNAL_STORAGE); } } + return changed; } - private boolean stepPattern(final RootStorage rootStorage, - final ExternalPatternInputSink externalPatternInputSink, - final StepBehavior stepBehavior, - final Map.Entry pattern) { + private PatternStepResult stepPattern(final RootStorage rootStorage, + final ExternalPatternInputSink externalPatternInputSink, + final StepBehavior stepBehavior, + final Map.Entry pattern) { + PatternStepResult result = PatternStepResult.IDLE; if (!stepBehavior.canStep(pattern.getKey())) { - return false; + return result; } final int steps = stepBehavior.getSteps(pattern.getKey()); for (int i = 0; i < steps; ++i) { - final boolean completed = pattern.getValue().step(internalStorage, rootStorage, externalPatternInputSink); - if (completed) { + final PatternStepResult stepResult = pattern.getValue().step( + internalStorage, + rootStorage, + externalPatternInputSink + ); + if (stepResult == PatternStepResult.COMPLETED) { LOGGER.debug("{} completed", pattern.getKey()); completedPatterns.add(pattern.getValue()); - return true; - } - } - return false; - } - - private void returnInternalStorageAndTryCompleteTask(final RootStorage rootStorage) { - if (returnInternalStorage(rootStorage)) { - updateState(TaskState.COMPLETED); - } - } - - @Override - public Collection copyInternalStorageState() { - return internalStorage.copyState(); - } - - private boolean extractInitialResources(final RootStorage rootStorage) { - boolean extractedAll = true; - final Set initialRequirementResources = new HashSet<>(initialRequirements.getAll()); - for (final ResourceKey initialRequirementResource : initialRequirementResources) { - final long needed = initialRequirements.get(initialRequirementResource); - final long extracted = rootStorage.extract(initialRequirementResource, needed, Action.EXECUTE, Actor.EMPTY); - LOGGER.debug("Extracted {}x {} from storage", extracted, initialRequirementResource); - if (extracted != needed) { - extractedAll = false; - } - if (extracted > 0) { - initialRequirements.remove(initialRequirementResource, extracted); - internalStorage.add(initialRequirementResource, extracted); + return stepResult; + } else { + result = result.and(stepResult); } } - return extractedAll; + return result; } - private boolean returnInternalStorage(final RootStorage rootStorage) { + private boolean returnInternalStorageAndTryCompleteTask(final RootStorage rootStorage) { boolean returnedAll = true; + boolean returnedAny = false; final Set internalResources = new HashSet<>(internalStorage.getAll()); for (final ResourceKey internalResource : internalResources) { final long internalAmount = internalStorage.get(internalResource); final long inserted = rootStorage.insert(internalResource, internalAmount, Action.EXECUTE, Actor.EMPTY); + if (inserted > 0) { + returnedAny = true; + } LOGGER.debug("Returned {}x {} into storage", inserted, internalResource); if (inserted != internalAmount) { returnedAll = false; @@ -200,7 +210,15 @@ private boolean returnInternalStorage(final RootStorage rootStorage) { internalStorage.remove(internalResource, inserted); } } - return returnedAll; + if (returnedAll) { + updateState(TaskState.COMPLETED); + } + return returnedAny; + } + + @Override + public Collection copyInternalStorageState() { + return internalStorage.copyState(); } @Override diff --git a/refinedstorage-network-api/src/main/java/com/refinedmods/refinedstorage/api/network/autocrafting/ParentContainer.java b/refinedstorage-network-api/src/main/java/com/refinedmods/refinedstorage/api/network/autocrafting/ParentContainer.java index e2c48244f..2a1fd59d4 100644 --- a/refinedstorage-network-api/src/main/java/com/refinedmods/refinedstorage/api/network/autocrafting/ParentContainer.java +++ b/refinedstorage-network-api/src/main/java/com/refinedmods/refinedstorage/api/network/autocrafting/ParentContainer.java @@ -1,6 +1,7 @@ package com.refinedmods.refinedstorage.api.network.autocrafting; import com.refinedmods.refinedstorage.api.autocrafting.Pattern; +import com.refinedmods.refinedstorage.api.autocrafting.task.Task; import org.apiguardian.api.API; @@ -11,4 +12,10 @@ public interface ParentContainer { void remove(PatternProvider provider, Pattern pattern); void update(Pattern pattern, int priority); + + void taskAdded(Task task); + + void taskRemoved(Task task); + + void taskChanged(Task task); } diff --git a/refinedstorage-network-api/src/main/java/com/refinedmods/refinedstorage/api/network/autocrafting/PatternProvider.java b/refinedstorage-network-api/src/main/java/com/refinedmods/refinedstorage/api/network/autocrafting/PatternProvider.java index a4cd85455..53a24e4fa 100644 --- a/refinedstorage-network-api/src/main/java/com/refinedmods/refinedstorage/api/network/autocrafting/PatternProvider.java +++ b/refinedstorage-network-api/src/main/java/com/refinedmods/refinedstorage/api/network/autocrafting/PatternProvider.java @@ -1,9 +1,12 @@ package com.refinedmods.refinedstorage.api.network.autocrafting; +import com.refinedmods.refinedstorage.api.autocrafting.status.TaskStatus; import com.refinedmods.refinedstorage.api.autocrafting.task.StepBehavior; import com.refinedmods.refinedstorage.api.autocrafting.task.Task; import com.refinedmods.refinedstorage.api.autocrafting.task.TaskId; +import java.util.List; + import org.apiguardian.api.API; @API(status = API.Status.STABLE, since = "2.0.0-milestone.4.8") @@ -19,4 +22,6 @@ default boolean contains(AutocraftingNetworkComponent component) { void addTask(Task task); void cancelTask(TaskId taskId); + + List getTaskStatuses(); } diff --git a/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImpl.java b/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImpl.java index 27df3c638..f0294f5f9 100644 --- a/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImpl.java +++ b/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImpl.java @@ -49,7 +49,8 @@ public class AutocraftingNetworkComponentImpl implements AutocraftingNetworkComp private final Set providers = new HashSet<>(); private final Map providerByPattern = new HashMap<>(); private final Map providerByTaskId = new HashMap<>(); - private final Set listeners = new HashSet<>(); + private final Set patternListeners = new HashSet<>(); + private final Set statusListeners = new HashSet<>(); private final PatternRepositoryImpl patternRepository = new PatternRepositoryImpl(); public AutocraftingNetworkComponentImpl(final Supplier rootStorageProvider, @@ -138,22 +139,22 @@ private TaskId startTask(final ResourceKey resource, @Override public void addListener(final PatternListener listener) { - listeners.add(listener); + patternListeners.add(listener); } @Override public void addListener(final TaskStatusListener listener) { - // TODO(feat): autocrafting monitor + statusListeners.add(listener); } @Override public void removeListener(final PatternListener listener) { - listeners.remove(listener); + patternListeners.remove(listener); } @Override public void removeListener(final TaskStatusListener listener) { - // TODO(feat): autocrafting monitor + statusListeners.remove(listener); } @Override @@ -168,8 +169,7 @@ public List getPatternsByOutput(final ResourceKey output) { @Override public List getStatuses() { - // TODO(feat): autocrafting monitor - return List.of(); + return providers.stream().map(PatternProvider::getTaskStatuses).flatMap(List::stream).toList(); } @Override @@ -180,7 +180,6 @@ public void cancel(final TaskId taskId) { } provider.cancelTask(taskId); providerByTaskId.remove(taskId); - // TODO(feat): autocrafting monitor } @Override @@ -191,19 +190,18 @@ public void cancelAll() { provider.cancelTask(taskId); } providerByTaskId.clear(); - // TODO(feat): autocrafting monitor } @Override public void add(final PatternProvider provider, final Pattern pattern, final int priority) { patternRepository.add(pattern, priority); providerByPattern.put(pattern, provider); - listeners.forEach(listener -> listener.onAdded(pattern)); + patternListeners.forEach(listener -> listener.onAdded(pattern)); } @Override public void remove(final PatternProvider provider, final Pattern pattern) { - listeners.forEach(listener -> listener.onRemoved(pattern)); + patternListeners.forEach(listener -> listener.onRemoved(pattern)); providerByPattern.remove(pattern); patternRepository.remove(pattern); } @@ -213,6 +211,25 @@ public void update(final Pattern pattern, final int priority) { patternRepository.update(pattern, priority); } + @Override + public void taskAdded(final Task task) { + statusListeners.forEach(listener -> listener.taskAdded(task.getStatus())); + } + + @Override + public void taskRemoved(final Task task) { + statusListeners.forEach(listener -> listener.taskRemoved(task.getId())); + } + + @Override + public void taskChanged(final Task task) { + if (statusListeners.isEmpty()) { + return; + } + final TaskStatus status = task.getStatus(); + statusListeners.forEach(listener -> listener.taskStatusChanged(status)); + } + // TODO(feat): processing pattern balancing @Override public boolean accept(final Pattern pattern, final Collection resources, final Action action) { diff --git a/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNode.java b/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNode.java index 1f2e30fd0..05a53b233 100644 --- a/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNode.java +++ b/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNode.java @@ -1,6 +1,7 @@ package com.refinedmods.refinedstorage.api.network.impl.node.patternprovider; import com.refinedmods.refinedstorage.api.autocrafting.Pattern; +import com.refinedmods.refinedstorage.api.autocrafting.status.TaskStatus; import com.refinedmods.refinedstorage.api.autocrafting.task.ExternalPatternInputSink; import com.refinedmods.refinedstorage.api.autocrafting.task.StepBehavior; import com.refinedmods.refinedstorage.api.autocrafting.task.Task; @@ -92,6 +93,7 @@ protected void onActiveChanged(final boolean newActive) { @Override public void onAddedIntoContainer(final ParentContainer parentContainer) { parents.add(parentContainer); + tasks.forEach(parentContainer::taskAdded); for (final Pattern pattern : patterns) { if (pattern != null) { parentContainer.add(this, pattern, priority); @@ -101,6 +103,7 @@ public void onAddedIntoContainer(final ParentContainer parentContainer) { @Override public void onRemovedFromContainer(final ParentContainer parentContainer) { + tasks.forEach(parentContainer::taskRemoved); parents.remove(parentContainer); for (final Pattern pattern : patterns) { if (pattern != null) { @@ -115,6 +118,7 @@ public void addTask(final Task task) { if (network != null) { setupTask(task, network.getComponent(StorageNetworkComponent.class)); } + parents.forEach(parent -> parent.taskAdded(task)); } @Override @@ -128,6 +132,11 @@ public void cancelTask(final TaskId taskId) { throw new IllegalArgumentException("Task %s not found".formatted(taskId)); } + @Override + public List getTaskStatuses() { + return tasks.stream().map(Task::getStatus).toList(); + } + private void setupTask(final Task task, final StorageNetworkComponent storage) { storage.addListener(task); } @@ -159,10 +168,13 @@ public void doWork() { AutocraftingNetworkComponent.class ); tasks.removeIf(task -> { - task.step(storage, outerExternalPatternInputSink, stepBehavior); + final boolean changed = task.step(storage, outerExternalPatternInputSink, stepBehavior); final boolean completed = task.getState() == TaskState.COMPLETED; if (completed) { cleanupTask(task, storage); + parents.forEach(parent -> parent.taskRemoved(task)); + } else if (changed) { + parents.forEach(parent -> parent.taskChanged(task)); } return completed; }); diff --git a/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayOutputNetworkNode.java b/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayOutputNetworkNode.java index 824f86f01..b99112abc 100644 --- a/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayOutputNetworkNode.java +++ b/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayOutputNetworkNode.java @@ -1,5 +1,6 @@ package com.refinedmods.refinedstorage.api.network.impl.node.relay; +import com.refinedmods.refinedstorage.api.autocrafting.status.TaskStatus; import com.refinedmods.refinedstorage.api.autocrafting.task.Task; import com.refinedmods.refinedstorage.api.autocrafting.task.TaskId; import com.refinedmods.refinedstorage.api.core.Action; @@ -23,6 +24,7 @@ import com.refinedmods.refinedstorage.api.storage.Storage; import java.util.Collection; +import java.util.List; import java.util.Set; import java.util.function.UnaryOperator; import javax.annotation.Nullable; @@ -144,6 +146,11 @@ public void cancelTask(final TaskId taskId) { // TODO(feat): relay support } + @Override + public List getTaskStatuses() { + return List.of(); // TODO(feat): relay support + } + @Override public SecurityDecision isAllowed(final Permission permission, final SecurityActor actor) { if (securityDelegate == null || securityDelegate.contains(securityDelegate)) { diff --git a/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayOutputPatternProvider.java b/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayOutputPatternProvider.java index 8ec210c4d..d5a9efb79 100644 --- a/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayOutputPatternProvider.java +++ b/refinedstorage-network/src/main/java/com/refinedmods/refinedstorage/api/network/impl/node/relay/RelayOutputPatternProvider.java @@ -1,6 +1,7 @@ package com.refinedmods.refinedstorage.api.network.impl.node.relay; import com.refinedmods.refinedstorage.api.autocrafting.Pattern; +import com.refinedmods.refinedstorage.api.autocrafting.status.TaskStatus; import com.refinedmods.refinedstorage.api.autocrafting.task.Task; import com.refinedmods.refinedstorage.api.autocrafting.task.TaskId; import com.refinedmods.refinedstorage.api.core.Action; @@ -16,6 +17,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.function.UnaryOperator; import java.util.stream.Collectors; @@ -104,6 +106,11 @@ public void cancelTask(final TaskId taskId) { // TODO(feat): relay support } + @Override + public List getTaskStatuses() { + return List.of(); // TODO(feat): relay support + } + @Override public void onAddedIntoContainer(final ParentContainer parentContainer) { if (delegate != null) { diff --git a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImplTest.java b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImplTest.java index 8decac5af..6a3ddb03a 100644 --- a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImplTest.java +++ b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/autocrafting/AutocraftingNetworkComponentImplTest.java @@ -4,6 +4,7 @@ import com.refinedmods.refinedstorage.api.autocrafting.preview.Preview; import com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewItem; import com.refinedmods.refinedstorage.api.autocrafting.preview.PreviewType; +import com.refinedmods.refinedstorage.api.autocrafting.status.TaskStatus; import com.refinedmods.refinedstorage.api.autocrafting.task.TaskId; import com.refinedmods.refinedstorage.api.autocrafting.task.TaskState; import com.refinedmods.refinedstorage.api.core.Action; @@ -30,6 +31,7 @@ import static com.refinedmods.refinedstorage.network.test.fixtures.ResourceFixtures.A; import static com.refinedmods.refinedstorage.network.test.fixtures.ResourceFixtures.B; import static com.refinedmods.refinedstorage.network.test.fixtures.ResourceFixtures.C; +import static com.refinedmods.refinedstorage.network.test.fixtures.ResourceFixtures.D; import static org.assertj.core.api.Assertions.assertThat; class AutocraftingNetworkComponentImplTest { @@ -260,4 +262,42 @@ void shouldNotStartTaskWhenThereAreMissingIngredients() { assertThat(taskId).isEmpty(); assertThat(provider.getTasks()).isEmpty(); } + + @Test + void shouldGetAllTaskStatuses() { + // Arrange + rootStorage.addSource(new StorageImpl()); + rootStorage.insert(A, 10, Action.EXECUTE, Actor.EMPTY); + + final PatternProviderNetworkNode provider1 = new PatternProviderNetworkNode(0, 5); + provider1.setPattern(1, pattern().ingredient(A, 3).output(B, 1).build()); + sut.onContainerAdded(() -> provider1); + + final PatternProviderNetworkNode provider2 = new PatternProviderNetworkNode(0, 5); + provider2.setPattern(1, pattern().ingredient(A, 3).output(C, 1).build()); + sut.onContainerAdded(() -> provider2); + + final PatternProviderNetworkNode provider3 = new PatternProviderNetworkNode(0, 5); + provider3.setPattern(1, pattern().ingredient(A, 3).output(D, 1).build()); + sut.onContainerAdded(() -> provider3); + + final Optional taskId1 = sut.startTask(B, 1, Actor.EMPTY, false).join(); + final Optional taskId2 = sut.startTask(C, 1, Actor.EMPTY, false).join(); + + sut.startTask(D, 1, Actor.EMPTY, false).join(); + sut.onContainerRemoved(() -> provider3); + + // Act + final List taskStatuses = sut.getStatuses(); + + // Assert + assertThat(taskId1).isPresent(); + assertThat(taskId2).isPresent(); + assertThat(provider1.getTasks()).hasSize(1); + assertThat(provider2.getTasks()).hasSize(1); + assertThat(taskStatuses) + .hasSize(2) + .anyMatch(ts -> ts.info().id().equals(taskId1.get())) + .anyMatch(ts -> ts.info().id().equals(taskId2.get())); + } } diff --git a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNodeTest.java b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNodeTest.java index 38e78f9a5..f4845d08d 100644 --- a/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNodeTest.java +++ b/refinedstorage-network/src/test/java/com/refinedmods/refinedstorage/api/network/impl/node/patternprovider/PatternProviderNetworkNodeTest.java @@ -2,7 +2,10 @@ import com.refinedmods.refinedstorage.api.autocrafting.Pattern; import com.refinedmods.refinedstorage.api.autocrafting.PatternType; +import com.refinedmods.refinedstorage.api.autocrafting.status.TaskStatus; +import com.refinedmods.refinedstorage.api.autocrafting.status.TaskStatusListener; import com.refinedmods.refinedstorage.api.autocrafting.task.StepBehavior; +import com.refinedmods.refinedstorage.api.autocrafting.task.TaskId; import com.refinedmods.refinedstorage.api.core.Action; import com.refinedmods.refinedstorage.api.network.Network; import com.refinedmods.refinedstorage.api.network.autocrafting.AutocraftingNetworkComponent; @@ -19,14 +22,23 @@ import com.refinedmods.refinedstorage.network.test.SetupNetwork; import com.refinedmods.refinedstorage.network.test.nodefactory.PatternProviderNetworkNodeFactory; +import java.util.Optional; + import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import static com.refinedmods.refinedstorage.api.autocrafting.PatternBuilder.pattern; import static com.refinedmods.refinedstorage.network.test.fixtures.ResourceFixtures.A; import static com.refinedmods.refinedstorage.network.test.fixtures.ResourceFixtures.B; import static com.refinedmods.refinedstorage.network.test.fixtures.ResourceFixtures.C; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; @NetworkTest @SetupNetwork @@ -418,6 +430,185 @@ void shouldInterceptNetworkInsertionsWhenWaitingForExternalPatternOutputs( ); } + @Test + void shouldNotifyStatusListenerWhenTaskIsAdded( + @InjectNetworkStorageComponent final StorageNetworkComponent storage, + @InjectNetworkAutocraftingComponent final AutocraftingNetworkComponent autocrafting + ) { + // Arrange + final TaskStatusListener listener = mock(TaskStatusListener.class); + autocrafting.addListener(listener); + + storage.addSource(new StorageImpl()); + storage.insert(C, 10, Action.EXECUTE, Actor.EMPTY); + + sut.setPattern(1, PATTERN_A); + + // Act + final var taskId = autocrafting.startTask(A, 1, Actor.EMPTY, false).join(); + + // Assert + assertThat(taskId).isPresent(); + final ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(TaskStatus.class); + verify(listener, times(1)).taskAdded(statusCaptor.capture()); + final TaskStatus status = statusCaptor.getValue(); + assertThat(status.info().id()).isEqualTo(taskId.get()); + } + + @Test + void shouldNotNotifyStatusListenerWhenTaskIsNotAddedDueToMissingResources( + @InjectNetworkAutocraftingComponent final AutocraftingNetworkComponent autocrafting + ) { + // Arrange + final TaskStatusListener listener = mock(TaskStatusListener.class); + autocrafting.addListener(listener); + + sut.setPattern(1, PATTERN_A); + + // Act + final var taskId = autocrafting.startTask(A, 1, Actor.EMPTY, false).join(); + + // Assert + assertThat(taskId).isEmpty(); + verify(listener, never()).taskAdded(any()); + } + + @Test + void shouldNotifyStatusListenerWhenTaskIsCompleted( + @InjectNetworkStorageComponent final StorageNetworkComponent storage, + @InjectNetworkAutocraftingComponent final AutocraftingNetworkComponent autocrafting + ) { + // Arrange + final TaskStatusListener listener = mock(TaskStatusListener.class); + autocrafting.addListener(listener); + + storage.addSource(new StorageImpl()); + storage.insert(C, 10, Action.EXECUTE, Actor.EMPTY); + + sut.setActive(true); + sut.setPattern(1, PATTERN_A); + + // Act & assert + final Optional createdId = autocrafting.startTask(A, 1, Actor.EMPTY, false).join(); + assertThat(createdId).isPresent(); + assertThat(sut.getTasks()).isNotEmpty(); + + sut.doWork(); + sut.doWork(); + assertThat(sut.getTasks()).isEmpty(); + + final ArgumentCaptor idCaptor = ArgumentCaptor.forClass(TaskId.class); + verify(listener, times(1)).taskRemoved(idCaptor.capture()); + final TaskId id = idCaptor.getValue(); + assertThat(createdId.get()).isEqualTo(id); + } + + @Test + void shouldNotifyStatusListenerWhenTaskHasChanged( + @InjectNetworkStorageComponent final StorageNetworkComponent storage, + @InjectNetworkAutocraftingComponent final AutocraftingNetworkComponent autocrafting + ) { + // Arrange + final TaskStatusListener listener = mock(TaskStatusListener.class); + autocrafting.addListener(listener); + + storage.addSource(new StorageImpl()); + storage.insert(A, 10, Action.EXECUTE, Actor.EMPTY); + + sut.setPattern(1, pattern(PatternType.EXTERNAL).ingredient(A, 1).output(B, 1).build()); + // swallow resources + sut.setExternalPatternInputSink((resources, action) -> true); + + // Act & assert + assertThat(autocrafting.startTask(B, 2, Actor.EMPTY, false).join()).isPresent(); + + sut.doWork(); + taskShouldBeMarkedAsChangedOnce(listener); + assertThat(sut.getTasks()).hasSize(1); + assertThat(sut.getTasks().getFirst().copyInternalStorageState()).containsExactlyInAnyOrder( + new ResourceAmount(A, 2) + ); + assertThat(storage.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + new ResourceAmount(A, 8) + ); + + sut.doWork(); + taskShouldBeMarkedAsChangedOnce(listener); + assertThat(sut.getTasks()).hasSize(1); + assertThat(sut.getTasks().getFirst().copyInternalStorageState()).containsExactlyInAnyOrder( + new ResourceAmount(A, 1) + ); + assertThat(storage.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + new ResourceAmount(A, 8) + ); + + sut.doWork(); + taskShouldBeMarkedAsChangedOnce(listener); + assertThat(sut.getTasks()).hasSize(1); + assertThat(sut.getTasks().getFirst().copyInternalStorageState()).isEmpty(); + assertThat(storage.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + new ResourceAmount(A, 8) + ); + + sut.doWork(); + taskShouldNotBeMarkedAsChanged(listener); + assertThat(sut.getTasks()).hasSize(1); + assertThat(sut.getTasks().getFirst().copyInternalStorageState()).isEmpty(); + assertThat(storage.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + new ResourceAmount(A, 8) + ); + + storage.insert(B, 1, Action.EXECUTE, Actor.EMPTY); + taskShouldNotBeMarkedAsChanged(listener); + assertThat(sut.getTasks()).hasSize(1); + assertThat(sut.getTasks().getFirst().copyInternalStorageState()).isEmpty(); + assertThat(storage.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + new ResourceAmount(A, 8), + new ResourceAmount(B, 1) + ); + + sut.doWork(); + taskShouldBeMarkedAsChangedOnce(listener); + assertThat(sut.getTasks()).hasSize(1); + assertThat(sut.getTasks().getFirst().copyInternalStorageState()).isEmpty(); + assertThat(storage.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + new ResourceAmount(A, 8), + new ResourceAmount(B, 1) + ); + + sut.doWork(); + taskShouldNotBeMarkedAsChanged(listener); + assertThat(sut.getTasks()).hasSize(1); + assertThat(sut.getTasks().getFirst().copyInternalStorageState()).isEmpty(); + assertThat(storage.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + new ResourceAmount(A, 8), + new ResourceAmount(B, 1) + ); + + storage.insert(B, 1, Action.EXECUTE, Actor.EMPTY); + taskShouldNotBeMarkedAsChanged(listener); + assertThat(sut.getTasks()).hasSize(1); + assertThat(sut.getTasks().getFirst().copyInternalStorageState()).isEmpty(); + assertThat(storage.getAll()).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder( + new ResourceAmount(A, 8), + new ResourceAmount(B, 2) + ); + + sut.doWork(); + taskShouldNotBeMarkedAsChanged(listener); + verify(listener, times(1)).taskRemoved(any()); + assertThat(sut.getTasks()).isEmpty(); + } + + private static void taskShouldBeMarkedAsChangedOnce(final TaskStatusListener listener) { + verify(listener, times(1)).taskStatusChanged(any()); + reset(listener); + } + + private static void taskShouldNotBeMarkedAsChanged(final TaskStatusListener listener) { + verify(listener, never()).taskStatusChanged(any()); + } + @Nested @SetupNetwork(id = "other") class NetworkChangeTest { @@ -546,6 +737,52 @@ void shouldInterceptInsertionsOnNewNetworkWhenNetworkChanges( new ResourceAmount(A, 3) ); } + + @Test + void shouldNotifyStatusListenersOfOldAndNewNetworkWhenNetworkChanges( + @InjectNetwork final Network network, + @InjectNetworkStorageComponent final StorageNetworkComponent storage, + @InjectNetworkAutocraftingComponent final AutocraftingNetworkComponent autocrafting, + @InjectNetwork("other") final Network otherNetwork, + @InjectNetworkAutocraftingComponent(networkId = "other") + final AutocraftingNetworkComponent otherAutocrafting + ) { + // Arrange + final TaskStatusListener listener = mock(TaskStatusListener.class); + autocrafting.addListener(listener); + + final TaskStatusListener otherListener = mock(TaskStatusListener.class); + otherAutocrafting.addListener(otherListener); + + storage.addSource(new StorageImpl()); + storage.insert(C, 10, Action.EXECUTE, Actor.EMPTY); + + sut.setPattern(1, PATTERN_A); + + // Act & assert + final var taskId = autocrafting.startTask(A, 1, Actor.EMPTY, false).join(); + assertThat(taskId).isPresent(); + final ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(TaskStatus.class); + verify(listener, times(1)).taskAdded(statusCaptor.capture()); + verify(listener, never()).taskRemoved(any()); + verify(otherListener, never()).taskAdded(any()); + verify(otherListener, never()).taskRemoved(any()); + final TaskStatus status = statusCaptor.getValue(); + assertThat(status.info().id()).isEqualTo(taskId.get()); + + reset(listener, otherListener); + + network.removeContainer(() -> sut); + sut.setNetwork(otherNetwork); + otherNetwork.addContainer(() -> sut); + + verify(listener, never()).taskAdded(any()); + final ArgumentCaptor removedIdCaptor = ArgumentCaptor.forClass(TaskId.class); + verify(listener, times(1)).taskRemoved(removedIdCaptor.capture()); + final ArgumentCaptor addedTaskCaptor = ArgumentCaptor.forClass(TaskStatus.class); + verify(otherListener, times(1)).taskAdded(addedTaskCaptor.capture()); + verify(otherListener, never()).taskRemoved(any()); + } } @Nested