Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependency-reduced-pom.xml
/LogiTech-master/
/GuguSlimefunLib/
/FinalTECH-Changed/
/AEbuild/

# docs
/docs/
Expand Down
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
<target>16</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
Expand Down Expand Up @@ -250,5 +255,11 @@
<version>3.45.3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import me.ddggdd135.guguslimefunlib.api.ItemHashMap;
Expand All @@ -20,6 +21,7 @@ public class MEStorageCellStorageData {
private ItemHashMap<Long> storages;
private long stored;
private long size;
private final AtomicLong dirtySequence = new AtomicLong();

public MEStorageCellStorageData() {}

Expand Down Expand Up @@ -75,6 +77,10 @@ public void setStored(long stored) {
this.stored = stored;
}

public long nextDirtySequence() {
return dirtySequence.incrementAndGet();
}

@Nullable public static MEStorageCellStorageData getMEStorageCellStorageData(@Nonnull UUID uuid) {
return cache.getOrDefault(uuid, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ public class AutoCraftingTask implements IDisposable {
private static final int MAX_LORE_LINES = 15;
private static final String MORE_ITEMS_INDICATOR = "&7... 还有%d项未显示";

public enum StepProcessResult {
PROGRESSED,
WAITING,
BLOCKED
}

public static final String CRAFTING_KEY = "auto_crafting";
private final UUID taskId;
private final CraftingRecipe recipe;
Expand All @@ -63,6 +69,10 @@ public class AutoCraftingTask implements IDisposable {
private volatile TaskState taskState = TaskState.RUNNING;
private final long createdAt;

public static boolean shouldIncrementGlobalFail(@Nonnull List<StepProcessResult> results) {
return !results.isEmpty() && results.stream().allMatch(result -> result == StepProcessResult.BLOCKED);
}

public AutoCraftingTask(@Nonnull NetworkInfo info, @Nonnull CraftingRecipe recipe, long count) {
this.taskId = UUID.randomUUID();
this.createdAt = System.currentTimeMillis();
Expand Down Expand Up @@ -328,9 +338,9 @@ public synchronized void suspend() {
if (device.isFinished(deviceBlock)) {
CraftingRecipe finished = device.getFinishedCraftingRecipe(deviceBlock);
if (finished != null && finished.equals(step.getRecipe())) {
device.finishCrafting(deviceBlock);
storage.addItem(step.getRecipe().getOutput());
toRemove.add(deviceLoc);
if (collectFinishedOutput(device, deviceBlock, step.getRecipe())) {
toRemove.add(deviceLoc);
}
}
}
}
Expand Down Expand Up @@ -549,16 +559,15 @@ public synchronized void moveNext(int maxDevices) {
return;
}

boolean anySuccess = false;
List<StepProcessResult> results = new ArrayList<>(readySteps.size());

for (CraftStep step : readySteps) {
boolean success = processStep(step, maxDevices);
if (success) anySuccess = true;
results.add(processStep(step, maxDevices));
}

if (anySuccess) {
if (results.contains(StepProcessResult.PROGRESSED)) {
globalFailTimes = 0;
} else {
} else if (shouldIncrementGlobalFail(results)) {
globalFailTimes++;
}

Expand Down Expand Up @@ -606,6 +615,15 @@ private void cleanupCompletedSteps() {
}
}

private boolean collectFinishedOutput(
@Nonnull IMERealCraftDevice device, @Nonnull Block deviceBlock, @Nonnull CraftingRecipe recipe) {
ItemStorage output = device.finishCrafting(deviceBlock);
ItemRequest[] outputRequests = ItemUtils.createRequests(recipe.getOutputAmounts());
if (!output.contains(outputRequests)) return false;
storage.addItem(output.getStorageUnsafe());
return true;
}

private void handleCancellation() {
boolean anyRunning = false;
for (CraftStep step : craftingSteps) {
Expand Down Expand Up @@ -643,9 +661,9 @@ private void handleCancellation() {
if (device.isFinished(deviceBlock)) {
CraftingRecipe finished = device.getFinishedCraftingRecipe(deviceBlock);
if (finished != null && finished.equals(nextRecipe)) {
device.finishCrafting(deviceBlock);
storage.addItem(nextRecipe.getOutput());
toRemove.add(deviceLoc);
if (collectFinishedOutput(device, deviceBlock, nextRecipe)) {
toRemove.add(deviceLoc);
}
}
}
}
Expand Down Expand Up @@ -692,15 +710,16 @@ private void handleCancellation() {
}
}

private boolean processStep(CraftStep step, int maxDevices) {
private StepProcessResult processStep(CraftStep step, int maxDevices) {
CraftingRecipe nextRecipe = step.getRecipe();
CraftType craftType = nextRecipe.getCraftType();
boolean doCraft = !isCancelling;
boolean hasProgress = false;
boolean hasWaiting = false;

if (step.getAmount() <= 0) {
if (step.isIdle()) {
return false;
return StepProcessResult.BLOCKED;
}
doCraft = false;
}
Expand All @@ -724,11 +743,17 @@ private boolean processStep(CraftStep step, int maxDevices) {
if (device.isFinished(deviceBlock)) {
CraftingRecipe finished = device.getFinishedCraftingRecipe(deviceBlock);
if (finished != null && finished.equals(nextRecipe)) {
device.finishCrafting(deviceBlock);
storage.addItem(finished.getOutput());
invalidDevices.add(deviceLoc);
hasProgress = true;
if (collectFinishedOutput(device, deviceBlock, finished)) {
invalidDevices.add(deviceLoc);
hasProgress = true;
} else {
hasWaiting = true;
}
} else {
hasWaiting = true;
}
} else {
hasWaiting = true;
}
}
for (Location loc : invalidDevices) {
Expand Down Expand Up @@ -831,12 +856,14 @@ private boolean processStep(CraftStep step, int maxDevices) {
storage.takeItem(requests);
step.setVirtualRunning(step.getVirtualRunning() + (int) actualAmount);
step.decreaseAmount(actualAmount);
return true;
return StepProcessResult.PROGRESSED;
}
}
}

return hasProgress;
if (hasProgress) return StepProcessResult.PROGRESSED;
if (hasWaiting || step.getVirtualRunning() > 0) return StepProcessResult.WAITING;
return StepProcessResult.BLOCKED;
}

public void showGUI(Player player) {
Expand Down Expand Up @@ -952,17 +979,18 @@ public void refreshGUI(int maxSize, boolean cancelButton) {
menu.getContents();
}

public void start() {
if (!SlimeAEPlugin.getNetworkData().AllNetworkData.contains(info)) return;
public boolean start() {
if (!SlimeAEPlugin.getNetworkData().AllNetworkData.contains(info)) return false;

if (info.getAutoCraftingSessions().contains(this)) return;
if (info.getAutoCraftingSessions().contains(this)) return true;

AutoCraftingTaskStartingEvent e = new AutoCraftingTaskStartingEvent(this);
Bukkit.getPluginManager().callEvent(e);

menu.addMenuCloseHandler(player -> {});

info.getAutoCraftingSessions().add(this);
return true;
}

public AEMenu getMenu() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,11 @@ public int tryRestore(NetworkInfo info) {
rec.isCancelling,
rec.createdAt);

task.start();
delete(rec.taskId);
restored++;
logger.info("Restored craft task " + rec.taskId);
if (task.start()) {
delete(rec.taskId);
restored++;
logger.info("Restored craft task " + rec.taskId);
}
} catch (Exception e) {
logger.log(Level.WARNING, "Failed to restore task " + rec.taskId + ", returning materials", e);
returnMaterialsFromRecord(rec, info);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,48 @@

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nonnull;

public class DirtyTracker {
private final ConcurrentHashMap<UUID, ConcurrentHashMap<Long, DirtyEntry>> dirtyMap = new ConcurrentHashMap<>();
private final AtomicReference<ConcurrentHashMap<UUID, ConcurrentHashMap<Long, DirtyEntry>>> dirtyMapRef =
new AtomicReference<>(new ConcurrentHashMap<>());
private final AtomicLong fallbackSequence = new AtomicLong();
private volatile Map<UUID, Map<Long, DirtyEntry>> pendingFlush = null;
private final ReentrantLock flushLock = new ReentrantLock();

public record DirtyEntry(char op, long newAmount, long timestamp) {}
public record DirtyEntry(char op, long newAmount, long timestamp, long sequence) {}

public void record(@Nonnull UUID cellUUID, long tplId, long newAmount, char op) {
dirtyMap.computeIfAbsent(cellUUID, k -> new ConcurrentHashMap<>())
.put(tplId, new DirtyEntry(op, newAmount, System.currentTimeMillis()));
record(cellUUID, tplId, newAmount, op, fallbackSequence.incrementAndGet());
}

public void record(@Nonnull UUID cellUUID, long tplId, long newAmount, char op, long sequence) {
DirtyEntry entry = new DirtyEntry(op, newAmount, System.currentTimeMillis(), sequence);
dirtyMapRef
.get()
.computeIfAbsent(cellUUID, k -> new ConcurrentHashMap<>())
.merge(tplId, entry, DirtyTracker::newerEntry);
}

public void recordDeleteCell(@Nonnull UUID cellUUID) {
ConcurrentHashMap<Long, DirtyEntry> cellMap = new ConcurrentHashMap<>();
cellMap.put(-1L, new DirtyEntry('D', 0, System.currentTimeMillis()));
dirtyMap.put(cellUUID, cellMap);
cellMap.put(-1L, new DirtyEntry('D', 0, System.currentTimeMillis(), fallbackSequence.incrementAndGet()));
dirtyMapRef.get().put(cellUUID, cellMap);
}

public List<JournalRow> drainPhase1() {
flushLock.lock();
try {
ConcurrentHashMap<UUID, ConcurrentHashMap<Long, DirtyEntry>> dirtyMap =
dirtyMapRef.getAndSet(new ConcurrentHashMap<>());
if (dirtyMap.isEmpty()) return Collections.emptyList();
Map<UUID, Map<Long, DirtyEntry>> snapshot = new HashMap<>();
for (var entry : dirtyMap.entrySet()) {
snapshot.put(entry.getKey(), new HashMap<>(entry.getValue()));
}
dirtyMap.clear();
pendingFlush = snapshot;
return toJournalRows(snapshot);
} finally {
Expand All @@ -55,12 +67,9 @@ public void rollbackFlush() {
for (var cellEntry : pendingFlush.entrySet()) {
UUID cellUUID = cellEntry.getKey();
ConcurrentHashMap<Long, DirtyEntry> cellMap =
dirtyMap.computeIfAbsent(cellUUID, k -> new ConcurrentHashMap<>());
dirtyMapRef.get().computeIfAbsent(cellUUID, k -> new ConcurrentHashMap<>());
for (var itemEntry : cellEntry.getValue().entrySet()) {
cellMap.merge(
itemEntry.getKey(),
itemEntry.getValue(),
(existing, pending) -> existing.timestamp() >= pending.timestamp() ? existing : pending);
cellMap.merge(itemEntry.getKey(), itemEntry.getValue(), DirtyTracker::newerEntry);
}
}
pendingFlush = null;
Expand All @@ -70,20 +79,25 @@ public void rollbackFlush() {
}

public boolean hasPendingChanges(UUID cellUUID) {
return dirtyMap.containsKey(cellUUID);
return dirtyMapRef.get().containsKey(cellUUID);
}

public List<JournalRow> drainCell(UUID cellUUID) {
flushLock.lock();
try {
ConcurrentHashMap<Long, DirtyEntry> cellMap = dirtyMap.remove(cellUUID);
ConcurrentHashMap<Long, DirtyEntry> cellMap = dirtyMapRef.get().remove(cellUUID);
if (cellMap == null) return Collections.emptyList();
return toJournalRows(Map.of(cellUUID, new HashMap<>(cellMap)));
} finally {
flushLock.unlock();
}
}

private static DirtyEntry newerEntry(DirtyEntry a, DirtyEntry b) {
if (a.sequence() != b.sequence()) return a.sequence() > b.sequence() ? a : b;
return a.timestamp() >= b.timestamp() ? a : b;
}

private List<JournalRow> toJournalRows(Map<UUID, Map<Long, DirtyEntry>> data) {
List<JournalRow> rows = new ArrayList<>();
for (var cellEntry : data.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,26 @@ public MEStorageCellStorageData loadData(@Nonnull ItemStack itemStack) {
}

public void markDirty(@Nonnull MEStorageCellStorageData data, @Nonnull ItemKey key, long finalAmount) {
markDirty(data, key, finalAmount, data.nextDirtySequence());
}

public void markDirty(
@Nonnull MEStorageCellStorageData data, @Nonnull ItemKey key, long finalAmount, long sequence) {
long tplId = bridge.getOrResolve(key);
dirtyTracker.record(data.getUuid(), tplId, finalAmount, finalAmount > 0 ? 'P' : 'R');
dirtyTracker.record(data.getUuid(), tplId, finalAmount, finalAmount > 0 ? 'P' : 'R', sequence);
}

public void markDirtyBatch(
@Nonnull MEStorageCellStorageData data, @Nonnull List<Map.Entry<ItemKey, Long>> entries) {
markDirtyBatch(data, entries, data.nextDirtySequence());
}

public void markDirtyBatch(
@Nonnull MEStorageCellStorageData data, @Nonnull List<Map.Entry<ItemKey, Long>> entries, long sequence) {
UUID cellUUID = data.getUuid();
for (Map.Entry<ItemKey, Long> entry : entries) {
long tplId = bridge.getOrResolve(entry.getKey());
dirtyTracker.record(cellUUID, tplId, entry.getValue(), entry.getValue() > 0 ? 'P' : 'R');
dirtyTracker.record(cellUUID, tplId, entry.getValue(), entry.getValue() > 0 ? 'P' : 'R', sequence);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import me.ddggdd135.slimeae.api.autocraft.CraftingRecipe;
import me.ddggdd135.slimeae.api.items.ItemStorage;
import org.bukkit.block.Block;

public interface IMERealCraftDevice extends IMECraftDevice {
Expand All @@ -14,5 +15,6 @@ public interface IMERealCraftDevice extends IMECraftDevice {

@Nullable CraftingRecipe getFinishedCraftingRecipe(@Nonnull Block block);

void finishCrafting(@Nonnull Block block);
@Nonnull
ItemStorage finishCrafting(@Nonnull Block block);
}
Loading
Loading