Add inventory, crafting and NBT optimizations with parity checks
This commit is contained in:
parent
a9cd6dd10e
commit
da43bf966b
40 changed files with 3132 additions and 0 deletions
29
src/main/java/dev/quorate/tune/ComponentMap.java
Normal file
29
src/main/java/dev/quorate/tune/ComponentMap.java
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.HashCommon;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||||
|
|
||||||
|
public final class ComponentMap<K, V> extends Reference2ObjectLinkedOpenHashMap<K, V> {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
// Deserialized maps use the original lookup because key identities change.
|
||||||
|
private final transient K hot;
|
||||||
|
private final transient int hash;
|
||||||
|
|
||||||
|
public ComponentMap(Reference2ObjectMap<K, V> map, K hot) {
|
||||||
|
super(map);
|
||||||
|
this.hot = hot;
|
||||||
|
hash = HashCommon.mix(System.identityHashCode(hot));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(Object component) {
|
||||||
|
if (component != null && component == hot) {
|
||||||
|
int slot = hash & mask;
|
||||||
|
var stored = key[slot];
|
||||||
|
if (stored == component) return value[slot];
|
||||||
|
if (stored == null) return defRetValue;
|
||||||
|
}
|
||||||
|
return super.get(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
154
src/main/java/dev/quorate/tune/FlowBatch.java
Normal file
154
src/main/java/dev/quorate/tune/FlowBatch.java
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import com.buuz135.functionalstorage.inventory.ControllerInventoryHandler;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||||
|
|
||||||
|
import mekanism.common.content.transporter.TransporterStack;
|
||||||
|
|
||||||
|
import net.minecraft.core.component.DataComponentPatch;
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.neoforged.neoforge.items.IItemHandler;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class FlowBatch {
|
||||||
|
private static final class State {
|
||||||
|
private final IItemHandler handler;
|
||||||
|
private final ItemStack[] inventory;
|
||||||
|
private final int[] sizes;
|
||||||
|
private final int[] limits;
|
||||||
|
private final boolean[] valid;
|
||||||
|
private final boolean[] certified;
|
||||||
|
private final int[] empty;
|
||||||
|
private final int emptyCount;
|
||||||
|
private final Reference2IntOpenHashMap<Item> first;
|
||||||
|
private Reference2ObjectOpenHashMap<Item, long[]> denied;
|
||||||
|
|
||||||
|
private State(IItemHandler handler, ItemStack[] inventory, int[] sizes) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.inventory = inventory.clone();
|
||||||
|
this.sizes = sizes.clone();
|
||||||
|
limits = new int[sizes.length];
|
||||||
|
Arrays.fill(limits, -1);
|
||||||
|
valid = new boolean[sizes.length];
|
||||||
|
certified = new boolean[sizes.length];
|
||||||
|
empty = new int[sizes.length];
|
||||||
|
first = new Reference2IntOpenHashMap<>(sizes.length);
|
||||||
|
first.defaultReturnValue(-1);
|
||||||
|
int count = 0;
|
||||||
|
for (int slot = 0; slot < sizes.length; slot++) {
|
||||||
|
if (sizes[slot] == 0) empty[count++] = slot;
|
||||||
|
else first.putIfAbsent(inventory[slot].getItem(), slot);
|
||||||
|
}
|
||||||
|
emptyCount = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int limit(int slot) {
|
||||||
|
if (limits[slot] == -1) limits[slot] = handler.getSlotLimit(slot);
|
||||||
|
return limits[slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean rejected(int ordinal, Item item, int count) {
|
||||||
|
if (denied == null) return false;
|
||||||
|
var masks = denied.get(item);
|
||||||
|
return masks != null && (masks[ordinal] & 1L << (count - 1)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reject(int ordinal, Item item, int count) {
|
||||||
|
if (denied == null) denied = new Reference2ObjectOpenHashMap<>();
|
||||||
|
denied.computeIfAbsent(item, _ -> new long[emptyCount])[ordinal] |= 1L << (count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean insert(ItemStack stack, int count) {
|
||||||
|
var item = stack.getItem();
|
||||||
|
int target = first.getInt(item);
|
||||||
|
for (int ordinal = 0; ordinal < emptyCount; ordinal++) {
|
||||||
|
int slot = empty[ordinal];
|
||||||
|
if (target >= 0 && slot >= target) break;
|
||||||
|
if (sizes[slot] > 0 || rejected(ordinal, item, count)) continue;
|
||||||
|
int limit = limit(slot);
|
||||||
|
if (limit == 0) continue;
|
||||||
|
if (limit < 0) return false;
|
||||||
|
if (!handler.isItemValid(slot, stack)) {
|
||||||
|
reject(ordinal, item, count);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var remainder = handler.insertItem(slot, stack, true);
|
||||||
|
if (remainder == ItemStack.EMPTY) {
|
||||||
|
if (count > limit) return false;
|
||||||
|
inventory[slot] = stack;
|
||||||
|
sizes[slot] = count;
|
||||||
|
first.put(item, slot);
|
||||||
|
valid[slot] = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (remainder.getCount() == count) {
|
||||||
|
reject(ordinal, item, count);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (target < 0) return false;
|
||||||
|
if (!plain(inventory[target])) return false;
|
||||||
|
if (!valid[target]) {
|
||||||
|
if (!handler.isItemValid(target, stack)) return false;
|
||||||
|
valid[target] = true;
|
||||||
|
}
|
||||||
|
int limit = limit(target);
|
||||||
|
long merged = (long) sizes[target] + count;
|
||||||
|
if (limit <= 0) return false;
|
||||||
|
if (merged > limit) return false;
|
||||||
|
if (limit > 64 && merged > 64 && !certified[target]) {
|
||||||
|
if (handler.insertItem(target, stack.copyWithCount(65), true) != ItemStack.EMPTY)
|
||||||
|
return false;
|
||||||
|
certified[target] = true;
|
||||||
|
}
|
||||||
|
sizes[target] = (int) merged;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commit(ItemStack[] target, int[] counts) {
|
||||||
|
System.arraycopy(inventory, 0, target, 0, target.length);
|
||||||
|
System.arraycopy(sizes, 0, counts, 0, counts.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FlowBatch() {}
|
||||||
|
|
||||||
|
private static boolean known(IItemHandler handler) {
|
||||||
|
if (handler instanceof ControllerInventoryHandler controller)
|
||||||
|
return handler.getClass()
|
||||||
|
.getName()
|
||||||
|
.equals(
|
||||||
|
"com.buuz135.functionalstorage.block.tile.StorageControllerTile$1")
|
||||||
|
&& controller.getDrawers().getItemHandlers().stream().allMatch(Handlers::leaf);
|
||||||
|
return Handlers.leaf(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean plain(ItemStack stack) {
|
||||||
|
return stack.getComponentsPatch() == DataComponentPatch.EMPTY
|
||||||
|
&& stack.get(DataComponents.CUSTOM_DATA) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean apply(
|
||||||
|
IItemHandler handler, ItemStack[] inventory, int[] sizes, Set<TransporterStack> flows) {
|
||||||
|
if (!known(handler)) return false;
|
||||||
|
for (int size : sizes) if (size < 0) return false;
|
||||||
|
var state = new State(handler, inventory, sizes);
|
||||||
|
for (var flow : flows) {
|
||||||
|
if (flow == null || !flow.getPathType().hasTarget()) continue;
|
||||||
|
var stack = flow.itemStack;
|
||||||
|
if (!plain(stack)) return false;
|
||||||
|
int count = stack.getCount();
|
||||||
|
if (count <= 0 || count > 64 || stack.getMaxStackSize() != 64) return false;
|
||||||
|
if (!state.insert(stack, count)) return false;
|
||||||
|
}
|
||||||
|
state.commit(inventory, sizes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/main/java/dev/quorate/tune/Handlers.java
Normal file
32
src/main/java/dev/quorate/tune/Handlers.java
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import com.buuz135.functionalstorage.inventory.ControllerInventoryHandler;
|
||||||
|
|
||||||
|
import net.neoforged.neoforge.items.IItemHandler;
|
||||||
|
|
||||||
|
public final class Handlers {
|
||||||
|
private Handlers() {}
|
||||||
|
|
||||||
|
public static boolean known(IItemHandler handler) {
|
||||||
|
if (handler instanceof ControllerInventoryHandler controller) {
|
||||||
|
if (!handler.getClass()
|
||||||
|
.getName()
|
||||||
|
.equals("com.buuz135.functionalstorage.block.tile.StorageControllerTile$1"))
|
||||||
|
return false;
|
||||||
|
return controller.getDrawers().getItemHandlers().stream()
|
||||||
|
.allMatch(
|
||||||
|
member -> member instanceof ControllerInventoryHandler || leaf(member));
|
||||||
|
}
|
||||||
|
return leaf(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean leaf(IItemHandler handler) {
|
||||||
|
return switch (handler.getClass().getName()) {
|
||||||
|
case "com.buuz135.functionalstorage.block.tile.DrawerTile$1",
|
||||||
|
"com.buuz135.functionalstorage.block.tile.CompactingDrawerTile$1",
|
||||||
|
"com.buuz135.functionalstorage.block.tile.SimpleCompactingDrawerTile$1" ->
|
||||||
|
true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/main/java/dev/quorate/tune/MemberList.java
Normal file
71
src/main/java/dev/quorate/tune/MemberList.java
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import java.util.AbstractList;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.RandomAccess;
|
||||||
|
|
||||||
|
public final class MemberList<E> extends AbstractList<E> implements RandomAccess {
|
||||||
|
private final ArrayList<E> entries;
|
||||||
|
private final Map<E, Integer> counts = new HashMap<>();
|
||||||
|
|
||||||
|
public MemberList(Collection<? extends E> entries) {
|
||||||
|
this.entries = new ArrayList<>(entries);
|
||||||
|
for (var entry : entries) addCount(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public E get(int index) {
|
||||||
|
return entries.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object value) {
|
||||||
|
return counts.containsKey(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(int index, E value) {
|
||||||
|
entries.add(index, value);
|
||||||
|
addCount(value);
|
||||||
|
modCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public E set(int index, E value) {
|
||||||
|
E old = entries.set(index, value);
|
||||||
|
removeCount(old);
|
||||||
|
addCount(value);
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public E remove(int index) {
|
||||||
|
E old = entries.remove(index);
|
||||||
|
removeCount(old);
|
||||||
|
modCount++;
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
entries.clear();
|
||||||
|
counts.clear();
|
||||||
|
modCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCount(E value) {
|
||||||
|
counts.merge(value, 1, Integer::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeCount(E value) {
|
||||||
|
counts.compute(value, (key, count) -> count == 1 ? null : count - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/main/java/dev/quorate/tune/NbtTree.java
Normal file
82
src/main/java/dev/quorate/tune/NbtTree.java
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.ByteArrayTag;
|
||||||
|
import net.minecraft.nbt.ByteTag;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.DoubleTag;
|
||||||
|
import net.minecraft.nbt.EndTag;
|
||||||
|
import net.minecraft.nbt.FloatTag;
|
||||||
|
import net.minecraft.nbt.IntArrayTag;
|
||||||
|
import net.minecraft.nbt.IntTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
import net.minecraft.nbt.LongArrayTag;
|
||||||
|
import net.minecraft.nbt.LongTag;
|
||||||
|
import net.minecraft.nbt.NbtOps;
|
||||||
|
import net.minecraft.nbt.ShortTag;
|
||||||
|
import net.minecraft.nbt.StringTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public final class NbtTree {
|
||||||
|
private NbtTree() {}
|
||||||
|
|
||||||
|
public static Tag convert(Tag input) {
|
||||||
|
if (input == null) return null;
|
||||||
|
// Tag subclasses may override copying; their encoding stays with the native codec.
|
||||||
|
var type = input.getClass();
|
||||||
|
return switch (input) {
|
||||||
|
case CompoundTag compound when type == CompoundTag.class -> {
|
||||||
|
var result = new CompoundTag();
|
||||||
|
for (var name : compound.getAllKeys()) {
|
||||||
|
if (name == null) yield null;
|
||||||
|
var value = convert(compound.get(name));
|
||||||
|
if (value == null) yield null;
|
||||||
|
result.put(name, value);
|
||||||
|
}
|
||||||
|
yield result;
|
||||||
|
}
|
||||||
|
case ListTag list when type == ListTag.class -> {
|
||||||
|
var entries = new ArrayList<Tag>(list.size());
|
||||||
|
for (var element : list) {
|
||||||
|
var value = element;
|
||||||
|
if (list.getElementType() == Tag.TAG_COMPOUND) {
|
||||||
|
if (element == null || element.getClass() != CompoundTag.class) yield null;
|
||||||
|
var compound = (CompoundTag) element;
|
||||||
|
if (compound.size() == 1 && compound.get("") != null)
|
||||||
|
value = compound.get("");
|
||||||
|
}
|
||||||
|
var converted = convert(value);
|
||||||
|
if (converted == null) yield null;
|
||||||
|
entries.add(converted);
|
||||||
|
}
|
||||||
|
yield NbtOps.INSTANCE.createList(entries.stream());
|
||||||
|
}
|
||||||
|
case ByteArrayTag array when type == ByteArrayTag.class -> {
|
||||||
|
var data = array.getAsByteArray();
|
||||||
|
yield data == null ? null : new ByteArrayTag(data.clone());
|
||||||
|
}
|
||||||
|
case IntArrayTag array when type == IntArrayTag.class -> {
|
||||||
|
var data = array.getAsIntArray();
|
||||||
|
yield data == null ? null : new IntArrayTag(data.clone());
|
||||||
|
}
|
||||||
|
case LongArrayTag array when type == LongArrayTag.class -> {
|
||||||
|
var data = array.getAsLongArray();
|
||||||
|
yield data == null ? null : new LongArrayTag(data.clone());
|
||||||
|
}
|
||||||
|
case ByteTag number when type == ByteTag.class -> ByteTag.valueOf(number.getAsByte());
|
||||||
|
case ShortTag number when type == ShortTag.class ->
|
||||||
|
ShortTag.valueOf(number.getAsShort());
|
||||||
|
case IntTag number when type == IntTag.class -> IntTag.valueOf(number.getAsInt());
|
||||||
|
case LongTag number when type == LongTag.class -> LongTag.valueOf(number.getAsLong());
|
||||||
|
case FloatTag number when type == FloatTag.class ->
|
||||||
|
FloatTag.valueOf(number.getAsFloat());
|
||||||
|
case DoubleTag number when type == DoubleTag.class ->
|
||||||
|
DoubleTag.valueOf(number.getAsDouble());
|
||||||
|
case StringTag string when type == StringTag.class ->
|
||||||
|
StringTag.valueOf(string.getAsString());
|
||||||
|
case EndTag _ when type == EndTag.class -> EndTag.INSTANCE;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/dev/quorate/tune/PlanRows.java
Normal file
20
src/main/java/dev/quorate/tune/PlanRows.java
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class PlanRows<K> {
|
||||||
|
private boolean shared;
|
||||||
|
|
||||||
|
public void share() {
|
||||||
|
shared = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void prepare(Map<Integer, Map<K, Long>> rows) {
|
||||||
|
if (!shared) return;
|
||||||
|
for (var entry : rows.entrySet()) {
|
||||||
|
entry.setValue(new LinkedHashMap<>(entry.getValue()));
|
||||||
|
}
|
||||||
|
shared = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/main/java/dev/quorate/tune/PlanShare.java
Normal file
5
src/main/java/dev/quorate/tune/PlanShare.java
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
public interface PlanShare {
|
||||||
|
void mctune$share();
|
||||||
|
}
|
||||||
24
src/main/java/dev/quorate/tune/Prediction.java
Normal file
24
src/main/java/dev/quorate/tune/Prediction.java
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import mekanism.common.content.transporter.TransporterStack;
|
||||||
|
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.neoforged.neoforge.items.IItemHandler;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface Prediction {
|
||||||
|
boolean mctune$batch(IItemHandler handler, Set<TransporterStack> flows);
|
||||||
|
|
||||||
|
boolean mctune$check(IItemHandler handler, int slot, ItemStack stack, boolean inFlight);
|
||||||
|
|
||||||
|
boolean mctune$matches(ItemStack left, ItemStack right);
|
||||||
|
|
||||||
|
int mctune$next(int slot, ItemStack stack, int count);
|
||||||
|
|
||||||
|
boolean mctune$begin(ItemStack stack, int count, boolean inFlight);
|
||||||
|
|
||||||
|
void mctune$finish(ItemStack stack, int count, boolean inFlight, int left);
|
||||||
|
|
||||||
|
ItemStack mctune$simulate(IItemHandler handler, int slot, ItemStack stack, boolean simulate);
|
||||||
|
}
|
||||||
54
src/main/java/dev/quorate/tune/SimCache.java
Normal file
54
src/main/java/dev/quorate/tune/SimCache.java
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class SimCache<K> {
|
||||||
|
private record Result<K>(K item, int count, int left) {}
|
||||||
|
|
||||||
|
private final Object[] items;
|
||||||
|
private final int[] counts;
|
||||||
|
private final int[] remaining;
|
||||||
|
private final List<List<Result<K>>> extra;
|
||||||
|
|
||||||
|
public SimCache(int slots) {
|
||||||
|
items = new Object[slots];
|
||||||
|
counts = new int[slots];
|
||||||
|
remaining = new int[slots];
|
||||||
|
extra = new ArrayList<>(Collections.nCopies(slots, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(int slot, K item, int count) {
|
||||||
|
if (items[slot] == item && counts[slot] == count) return remaining[slot];
|
||||||
|
var results = extra.get(slot);
|
||||||
|
if (results != null)
|
||||||
|
for (var result : results) {
|
||||||
|
if (result.item() == item && result.count() == count) return result.left();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(int slot, K item, int count, int left) {
|
||||||
|
if (items[slot] == null || items[slot] == item && counts[slot] == count) {
|
||||||
|
items[slot] = item;
|
||||||
|
counts[slot] = count;
|
||||||
|
remaining[slot] = left;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var results = extra.get(slot);
|
||||||
|
if (results == null) {
|
||||||
|
results = new ArrayList<>();
|
||||||
|
extra.set(slot, results);
|
||||||
|
}
|
||||||
|
var entry = new Result<>(item, count, left);
|
||||||
|
for (int index = 0; index < results.size(); index++) {
|
||||||
|
var result = results.get(index);
|
||||||
|
if (result.item() == item && result.count() == count) {
|
||||||
|
results.set(index, entry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
310
src/main/java/dev/quorate/tune/SlotMemo.java
Normal file
310
src/main/java/dev/quorate/tune/SlotMemo.java
Normal file
|
|
@ -0,0 +1,310 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import mekanism.common.util.InventoryUtils;
|
||||||
|
|
||||||
|
import net.minecraft.core.component.DataComponentPatch;
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
|
import net.neoforged.neoforge.items.IItemHandler;
|
||||||
|
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class SlotMemo {
|
||||||
|
private static final int VALID = 1;
|
||||||
|
private static final int INVALID = 2;
|
||||||
|
private static final int MATCH = 4;
|
||||||
|
private static final int DIFFERENT = 8;
|
||||||
|
|
||||||
|
private record StackKey(Item item, DataComponentPatch data, boolean empty) {}
|
||||||
|
|
||||||
|
private record AppendRule(int slot, int limit, int maxStack, ItemStack stack) {}
|
||||||
|
|
||||||
|
private static final class CountState {
|
||||||
|
private final byte[] valid;
|
||||||
|
private final BitSet slots;
|
||||||
|
private AppendRule line;
|
||||||
|
|
||||||
|
private CountState(int size, BitSet slots) {
|
||||||
|
valid = new byte[size];
|
||||||
|
this.slots = slots;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record StackState(
|
||||||
|
StackKey key, byte[] matches, BitSet slots, Map<Integer, CountState> rows) {}
|
||||||
|
|
||||||
|
// InventoryInfo confines cached decisions to one TransporterManager prediction snapshot.
|
||||||
|
private final ItemStack[] inventory;
|
||||||
|
private final int[] sizes;
|
||||||
|
private final int[] limits;
|
||||||
|
private final int[] actual;
|
||||||
|
private final SimCache<Item> trials;
|
||||||
|
private final Item[] kinds;
|
||||||
|
private final boolean[] compared;
|
||||||
|
private final BitSet eligible;
|
||||||
|
private final BitSet universal;
|
||||||
|
private final Map<Item, BitSet> indices;
|
||||||
|
private final Map<StackKey, StackState> types = new HashMap<>();
|
||||||
|
private int offered = -1;
|
||||||
|
private ItemStack before;
|
||||||
|
private int beforeSize;
|
||||||
|
private int capacity;
|
||||||
|
private boolean stable;
|
||||||
|
private boolean changed;
|
||||||
|
private StackState type;
|
||||||
|
private CountState row;
|
||||||
|
private ItemStack selected;
|
||||||
|
private int selectedCount;
|
||||||
|
|
||||||
|
public SlotMemo(
|
||||||
|
ItemStack[] inventory, int[] sizes, int[] actual, int[] limits, boolean inFlight) {
|
||||||
|
indices = inFlight ? new IdentityHashMap<>() : null;
|
||||||
|
this.inventory = inventory;
|
||||||
|
this.sizes = sizes;
|
||||||
|
this.limits = limits;
|
||||||
|
this.actual = actual;
|
||||||
|
trials = new SimCache<>(inventory.length);
|
||||||
|
kinds = new Item[inventory.length];
|
||||||
|
compared = new boolean[inventory.length];
|
||||||
|
eligible = new BitSet(inventory.length);
|
||||||
|
eligible.set(0, inventory.length);
|
||||||
|
universal = new BitSet(inventory.length);
|
||||||
|
for (int slot = 0; slot < inventory.length; slot++) {
|
||||||
|
var item = sizes[slot] > 0 && actual[slot] > 0 ? inventory[slot].getItem() : null;
|
||||||
|
if (item == Items.AIR) item = null;
|
||||||
|
kinds[slot] = item;
|
||||||
|
if (item == null) universal.set(slot);
|
||||||
|
else if (indices != null) index(item).set(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BitSet index(Item item) {
|
||||||
|
var slots = indices.get(item);
|
||||||
|
if (slots == null) {
|
||||||
|
slots = new BitSet(inventory.length);
|
||||||
|
indices.put(item, slots);
|
||||||
|
}
|
||||||
|
return slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StackState create(StackKey key) {
|
||||||
|
var slots = key.empty() ? (BitSet) eligible.clone() : (BitSet) universal.clone();
|
||||||
|
if (!key.empty()) {
|
||||||
|
if (indices == null) {
|
||||||
|
for (int slot = 0; slot < kinds.length; slot++)
|
||||||
|
if (kinds[slot] == key.item()) slots.set(slot);
|
||||||
|
} else {
|
||||||
|
var matching = indices.get(key.item());
|
||||||
|
if (matching != null) slots.or(matching);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slots.and(eligible);
|
||||||
|
return new StackState(key, new byte[inventory.length], slots, HashMap.newHashMap(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void select(ItemStack stack) {
|
||||||
|
if (selected == stack) return;
|
||||||
|
int count = stack.getCount();
|
||||||
|
var item = stack.getItem();
|
||||||
|
var data = stack.getComponentsPatch();
|
||||||
|
boolean empty = count <= 0 || item == Items.AIR;
|
||||||
|
boolean changed =
|
||||||
|
data != DataComponentPatch.EMPTY
|
||||||
|
|| type == null
|
||||||
|
|| type.key().data() != data
|
||||||
|
|| type.key().item() != item
|
||||||
|
|| type.key().empty() != empty;
|
||||||
|
if (changed) {
|
||||||
|
var key = new StackKey(item, data, empty);
|
||||||
|
type = types.get(key);
|
||||||
|
if (type == null) {
|
||||||
|
type = create(key);
|
||||||
|
types.put(key, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed || selectedCount != count) {
|
||||||
|
row = type.rows().get(count);
|
||||||
|
if (row == null) {
|
||||||
|
row = new CountState(inventory.length, (BitSet) type.slots().clone());
|
||||||
|
type.rows().put(count, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedCount = count;
|
||||||
|
selected = stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean check(IItemHandler handler, int slot, ItemStack stack, int limit) {
|
||||||
|
offer(slot);
|
||||||
|
int size = sizes[slot];
|
||||||
|
if (size > 0 && size >= limit) {
|
||||||
|
if (limit != -1) exclude(slot);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (size <= 0 && compared[slot]) reset(slot);
|
||||||
|
select(stack);
|
||||||
|
if (!row.slots.get(slot)) return false;
|
||||||
|
if (size > 0) {
|
||||||
|
int state = type.matches()[slot];
|
||||||
|
if (state == 0) {
|
||||||
|
state =
|
||||||
|
InventoryUtils.areItemsStackable(inventory[slot], stack)
|
||||||
|
? MATCH
|
||||||
|
: DIFFERENT;
|
||||||
|
type.matches()[slot] = (byte) state;
|
||||||
|
compared[slot] = true;
|
||||||
|
}
|
||||||
|
if (state == DIFFERENT) {
|
||||||
|
type.slots().clear(slot);
|
||||||
|
for (var value : type.rows().values()) value.slots.clear(slot);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int valid = row.valid[slot];
|
||||||
|
if (valid == 0) {
|
||||||
|
valid = handler.isItemValid(slot, stack) ? VALID : INVALID;
|
||||||
|
row.valid[slot] = (byte) valid;
|
||||||
|
}
|
||||||
|
if (valid == INVALID) row.slots.clear(slot);
|
||||||
|
return valid == VALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flush() {
|
||||||
|
if (offered < 0) return;
|
||||||
|
int slot = offered;
|
||||||
|
offered = -1;
|
||||||
|
changed |= sizes[slot] != beforeSize;
|
||||||
|
int limit = limits[slot];
|
||||||
|
if (limit != -1 && (limit == 0 || sizes[slot] > 0 && sizes[slot] >= limit)) {
|
||||||
|
exclude(slot);
|
||||||
|
}
|
||||||
|
refresh(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int next(int start, ItemStack stack, int count) {
|
||||||
|
flush();
|
||||||
|
if (count == 0 || start >= inventory.length) return start;
|
||||||
|
if (selected != stack
|
||||||
|
&& stack.getComponentsPatch() == DataComponentPatch.EMPTY
|
||||||
|
&& stack.get(DataComponents.CUSTOM_DATA) == null) select(stack);
|
||||||
|
var slots = selected == stack ? row.slots : eligible;
|
||||||
|
int slot = slots.nextSetBit(start);
|
||||||
|
if (slot >= 0) offer(slot);
|
||||||
|
return slot < 0 ? inventory.length : slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean plain(ItemStack stack) {
|
||||||
|
return stack.getComponentsPatch() == DataComponentPatch.EMPTY
|
||||||
|
&& stack.get(DataComponents.CUSTOM_DATA) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean begin(ItemStack stack, int count, boolean inFlight) {
|
||||||
|
changed = false;
|
||||||
|
if (!inFlight || count <= 0 || !plain(stack)) return false;
|
||||||
|
select(stack);
|
||||||
|
var line = row.line;
|
||||||
|
if (line == null
|
||||||
|
|| selectedCount != count
|
||||||
|
|| row.slots.nextSetBit(0) != line.slot()
|
||||||
|
|| inventory[line.slot()] != line.stack()
|
||||||
|
|| sizes[line.slot()] <= line.maxStack()
|
||||||
|
|| (long) sizes[line.slot()] + count > line.limit()) return false;
|
||||||
|
sizes[line.slot()] += count;
|
||||||
|
if (sizes[line.slot()] >= limits[line.slot()]) exclude(line.slot());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finish(ItemStack stack, int count, boolean inFlight, int left) {
|
||||||
|
if (inFlight
|
||||||
|
&& left == 0
|
||||||
|
&& !changed
|
||||||
|
&& offered >= 0
|
||||||
|
&& stable
|
||||||
|
&& count > 0
|
||||||
|
&& selected == stack
|
||||||
|
&& selectedCount == count
|
||||||
|
&& beforeSize > stack.getMaxStackSize()
|
||||||
|
&& (long) sizes[offered] - beforeSize == count
|
||||||
|
&& row.slots.nextSetBit(0) == offered) {
|
||||||
|
row.line =
|
||||||
|
new AppendRule(offered, capacity, stack.getMaxStackSize(), inventory[offered]);
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemStack simulate(IItemHandler handler, int slot, ItemStack stack, boolean simulate) {
|
||||||
|
if (!plain(stack)) return handler.insertItem(slot, stack, simulate);
|
||||||
|
int count = stack.getCount();
|
||||||
|
var item = stack.getItem();
|
||||||
|
int left = trials.get(slot, item, count);
|
||||||
|
ItemStack result;
|
||||||
|
if (left < 0) {
|
||||||
|
result = handler.insertItem(slot, stack, simulate);
|
||||||
|
left = result.getCount();
|
||||||
|
trials.put(slot, item, count, left);
|
||||||
|
} else
|
||||||
|
result =
|
||||||
|
left == count ? stack : left == 0 ? ItemStack.EMPTY : stack.copyWithCount(left);
|
||||||
|
int accepted = count - left;
|
||||||
|
capacity =
|
||||||
|
accepted < count ? Math.min(limits[slot], actual[slot] + accepted) : limits[slot];
|
||||||
|
stable = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void offer(int slot) {
|
||||||
|
offered = slot;
|
||||||
|
before = inventory[slot];
|
||||||
|
beforeSize = sizes[slot];
|
||||||
|
stable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh(int slot) {
|
||||||
|
var previous = kinds[slot];
|
||||||
|
Item current = previous;
|
||||||
|
if (sizes[slot] <= 0) current = null;
|
||||||
|
else if (inventory[slot] != before || previous == null) {
|
||||||
|
var stack = inventory[slot];
|
||||||
|
current = stack.isEmpty() ? null : stack.getItem();
|
||||||
|
}
|
||||||
|
if (current != previous) {
|
||||||
|
if (previous == null) universal.clear(slot);
|
||||||
|
else if (indices != null) indices.get(previous).clear(slot);
|
||||||
|
if (current == null) universal.set(slot);
|
||||||
|
else if (indices != null) index(current).set(slot);
|
||||||
|
kinds[slot] = current;
|
||||||
|
reset(slot);
|
||||||
|
} else if (sizes[slot] <= 0 && compared[slot]) reset(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exclude(int slot) {
|
||||||
|
if (!eligible.get(slot)) return;
|
||||||
|
eligible.clear(slot);
|
||||||
|
for (var value : types.values()) {
|
||||||
|
value.slots().clear(slot);
|
||||||
|
for (var entry : value.rows().values()) entry.slots.clear(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset(int slot) {
|
||||||
|
// Overflow can empty a virtual slot and permit assignment to another item.
|
||||||
|
for (var value : types.values()) {
|
||||||
|
value.matches()[slot] = 0;
|
||||||
|
boolean allowed =
|
||||||
|
eligible.get(slot)
|
||||||
|
&& (value.key().empty()
|
||||||
|
|| kinds[slot] == null
|
||||||
|
|| value.key().item() == kinds[slot]);
|
||||||
|
value.slots().set(slot, allowed);
|
||||||
|
for (var entry : value.rows().values()) {
|
||||||
|
entry.slots.set(slot, allowed && entry.valid[slot] != INVALID);
|
||||||
|
entry.line = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compared[slot] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/main/java/dev/quorate/tune/TreeCodec.java
Normal file
42
src/main/java/dev/quorate/tune/TreeCodec.java
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
|
import com.mojang.serialization.DataResult;
|
||||||
|
import com.mojang.serialization.Dynamic;
|
||||||
|
import com.mojang.serialization.DynamicOps;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.NbtOps;
|
||||||
|
import net.minecraft.resources.RegistryOps;
|
||||||
|
|
||||||
|
public final class TreeCodec implements Codec<CompoundTag> {
|
||||||
|
private final Codec<CompoundTag> original;
|
||||||
|
|
||||||
|
public TreeCodec(Codec<CompoundTag> original) {
|
||||||
|
this.original = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> DataResult<Pair<CompoundTag, T>> decode(DynamicOps<T> ops, T input) {
|
||||||
|
return original.decode(ops, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> DataResult<T> encode(CompoundTag input, DynamicOps<T> ops, T prefix) {
|
||||||
|
if (ops.getClass() == RegistryOps.class
|
||||||
|
&& ops instanceof RegistryOps<?> registry
|
||||||
|
&& registry.withParent(NbtOps.INSTANCE) == registry) {
|
||||||
|
var converted = NbtTree.convert(input);
|
||||||
|
if (converted != null)
|
||||||
|
return Codec.PASSTHROUGH.encode(new Dynamic<>(ops, (T) converted), ops, prefix);
|
||||||
|
}
|
||||||
|
return original.encode(input, ops, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return original.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/main/java/dev/quorate/tune/TuneMod.java
Normal file
6
src/main/java/dev/quorate/tune/TuneMod.java
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
package dev.quorate.tune;
|
||||||
|
|
||||||
|
import net.neoforged.fml.common.Mod;
|
||||||
|
|
||||||
|
@Mod("mc_tune")
|
||||||
|
public final class TuneMod {}
|
||||||
39
src/main/java/dev/quorate/tune/mixin/AlmanacMixin.java
Normal file
39
src/main/java/dev/quorate/tune/mixin/AlmanacMixin.java
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package dev.quorate.tune.mixin;
|
||||||
|
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Mixin(targets = "com.frikinjay.almanac.util.ItemNBTUtil", remap = false)
|
||||||
|
public abstract class AlmanacMixin {
|
||||||
|
@Redirect(
|
||||||
|
method = "checkAndFix",
|
||||||
|
at =
|
||||||
|
@At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target = "Ljava/util/Set;contains(Ljava/lang/Object;)Z",
|
||||||
|
ordinal = 0),
|
||||||
|
require = 1,
|
||||||
|
allow = 1)
|
||||||
|
private static boolean mctune$unchanged(Set<?> cache, Object item, ItemStack stack) {
|
||||||
|
var data = stack.get(DataComponents.CUSTOM_DATA);
|
||||||
|
// The exclusion branch avoids cancellation callbacks when custom data cannot change.
|
||||||
|
return data == null || !data.isEmpty() || cache.contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Overwrite(remap = false)
|
||||||
|
private static boolean tryRemoveEmptyCustomData(ItemStack stack) {
|
||||||
|
var data = stack.get(DataComponents.CUSTOM_DATA);
|
||||||
|
if (data != null && data.isEmpty()) {
|
||||||
|
stack.remove(DataComponents.CUSTOM_DATA);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/main/java/dev/quorate/tune/mixin/ComponentsMixin.java
Normal file
33
src/main/java/dev/quorate/tune/mixin/ComponentsMixin.java
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package dev.quorate.tune.mixin;
|
||||||
|
|
||||||
|
import dev.quorate.tune.ComponentMap;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||||
|
|
||||||
|
import net.minecraft.core.component.DataComponentType;
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
|
|
||||||
|
@Mixin(targets = "net.minecraft.core.component.DataComponentMap$Builder", remap = false)
|
||||||
|
public abstract class ComponentsMixin {
|
||||||
|
@ModifyArg(
|
||||||
|
method = "buildFromMapTrusted",
|
||||||
|
at =
|
||||||
|
@At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target =
|
||||||
|
"Lnet/minecraft/core/component/DataComponentMap$Builder$SimpleMap;<init>(Lit/unimi/dsi/fastutil/objects/Reference2ObjectMap;)V"),
|
||||||
|
index = 0,
|
||||||
|
require = 2,
|
||||||
|
allow = 2)
|
||||||
|
private static Reference2ObjectMap<DataComponentType<?>, Object> mctune$indexed(
|
||||||
|
Reference2ObjectMap<DataComponentType<?>, Object> map) {
|
||||||
|
return map instanceof Reference2ObjectArrayMap<?, ?>
|
||||||
|
? new ComponentMap<>(map, DataComponents.CUSTOM_DATA)
|
||||||
|
: map;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/java/dev/quorate/tune/mixin/DrawersMixin.java
Normal file
31
src/main/java/dev/quorate/tune/mixin/DrawersMixin.java
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package dev.quorate.tune.mixin;
|
||||||
|
|
||||||
|
import dev.quorate.tune.MemberList;
|
||||||
|
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Coerce;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mixin(targets = "com.buuz135.functionalstorage.util.ConnectedDrawers", remap = false)
|
||||||
|
public abstract class DrawersMixin {
|
||||||
|
@Shadow private List<Object> itemHandlers;
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = {"<init>", "rebuild"},
|
||||||
|
at =
|
||||||
|
@At(
|
||||||
|
value = "FIELD",
|
||||||
|
target =
|
||||||
|
"Lcom/buuz135/functionalstorage/util/ConnectedDrawers;itemHandlers:Ljava/util/List;",
|
||||||
|
opcode = Opcodes.PUTFIELD),
|
||||||
|
require = 2,
|
||||||
|
allow = 2)
|
||||||
|
private void mctune$members(@Coerce Object drawers, List<Object> entries) {
|
||||||
|
itemHandlers = new MemberList<>(entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/main/java/dev/quorate/tune/mixin/InvMixin.java
Normal file
85
src/main/java/dev/quorate/tune/mixin/InvMixin.java
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
package dev.quorate.tune.mixin;
|
||||||
|
|
||||||
|
import dev.quorate.tune.FlowBatch;
|
||||||
|
import dev.quorate.tune.Handlers;
|
||||||
|
import dev.quorate.tune.Prediction;
|
||||||
|
import dev.quorate.tune.SlotMemo;
|
||||||
|
|
||||||
|
import mekanism.common.content.transporter.TransporterStack;
|
||||||
|
import mekanism.common.util.InventoryUtils;
|
||||||
|
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.neoforged.neoforge.items.IItemHandler;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Mixin(
|
||||||
|
targets = "mekanism.common.content.transporter.TransporterManager$InventoryInfo",
|
||||||
|
remap = false)
|
||||||
|
public abstract class InvMixin implements Prediction {
|
||||||
|
@Shadow @Final private ItemStack[] inventory;
|
||||||
|
@Shadow @Final private int[] stackSizes;
|
||||||
|
@Shadow @Final private int[] actualStackSizes;
|
||||||
|
@Shadow @Final private int[] slotLimits;
|
||||||
|
@Unique private SlotMemo mctune$memo;
|
||||||
|
@Unique private boolean mctune$native;
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"), require = 1, allow = 1)
|
||||||
|
private void mctune$domain(IItemHandler handler, CallbackInfo callback) {
|
||||||
|
mctune$native = !Handlers.known(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract int getSlotLimit(IItemHandler handler, int slot);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mctune$batch(IItemHandler handler, Set<TransporterStack> flows) {
|
||||||
|
return !mctune$native && FlowBatch.apply(handler, inventory, stackSizes, flows);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mctune$check(IItemHandler handler, int slot, ItemStack stack, boolean inFlight) {
|
||||||
|
if (mctune$native) return handler.isItemValid(slot, stack);
|
||||||
|
if (mctune$memo == null) {
|
||||||
|
mctune$memo =
|
||||||
|
new SlotMemo(inventory, stackSizes, actualStackSizes, slotLimits, inFlight);
|
||||||
|
}
|
||||||
|
return mctune$memo.check(handler, slot, stack, getSlotLimit(handler, slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mctune$matches(ItemStack left, ItemStack right) {
|
||||||
|
return !mctune$native || InventoryUtils.areItemsStackable(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int mctune$next(int slot, ItemStack stack, int count) {
|
||||||
|
return mctune$memo == null ? slot : mctune$memo.next(slot, stack, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mctune$begin(ItemStack stack, int count, boolean inFlight) {
|
||||||
|
return mctune$memo != null && mctune$memo.begin(stack, count, inFlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mctune$finish(ItemStack stack, int count, boolean inFlight, int left) {
|
||||||
|
if (mctune$memo != null) mctune$memo.finish(stack, count, inFlight, left);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack mctune$simulate(
|
||||||
|
IItemHandler handler, int slot, ItemStack stack, boolean simulate) {
|
||||||
|
return mctune$native
|
||||||
|
? handler.insertItem(slot, stack, simulate)
|
||||||
|
: mctune$memo.simulate(handler, slot, stack, simulate);
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/main/java/dev/quorate/tune/mixin/MekMixin.java
Normal file
137
src/main/java/dev/quorate/tune/mixin/MekMixin.java
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
package dev.quorate.tune.mixin;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||||
|
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||||
|
|
||||||
|
import dev.quorate.tune.Prediction;
|
||||||
|
|
||||||
|
import mekanism.common.content.transporter.TransporterStack;
|
||||||
|
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.core.GlobalPos;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.neoforged.neoforge.items.IItemHandler;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Coerce;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Mixin(targets = "mekanism.common.content.transporter.TransporterManager", remap = false)
|
||||||
|
public abstract class MekMixin {
|
||||||
|
@WrapMethod(method = "predictFlowing")
|
||||||
|
private static boolean mctune$batch(
|
||||||
|
GlobalPos position,
|
||||||
|
Direction side,
|
||||||
|
IItemHandler handler,
|
||||||
|
@Coerce Object info,
|
||||||
|
Map<GlobalPos, Set<TransporterStack>> flowing,
|
||||||
|
Operation<Boolean> original) {
|
||||||
|
var stacks = flowing.get(position);
|
||||||
|
if (stacks != null
|
||||||
|
&& !stacks.isEmpty()
|
||||||
|
&& ((Prediction) info).mctune$batch(handler, stacks)) return true;
|
||||||
|
return original.call(position, side, handler, info, flowing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WrapMethod(method = "simulateInsert")
|
||||||
|
private static int mctune$flow(
|
||||||
|
IItemHandler handler,
|
||||||
|
@Coerce Object info,
|
||||||
|
ItemStack stack,
|
||||||
|
int count,
|
||||||
|
boolean inFlight,
|
||||||
|
Operation<Integer> original) {
|
||||||
|
var prediction = (Prediction) info;
|
||||||
|
if (prediction.mctune$begin(stack, count, inFlight)) return 0;
|
||||||
|
int left = original.call(handler, info, stack, count, inFlight);
|
||||||
|
prediction.mctune$finish(stack, count, inFlight, left);
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = "simulateInsert",
|
||||||
|
at =
|
||||||
|
@At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target =
|
||||||
|
"Lnet/neoforged/neoforge/items/IItemHandler;insertItem(ILnet/minecraft/world/item/ItemStack;Z)Lnet/minecraft/world/item/ItemStack;",
|
||||||
|
remap = false),
|
||||||
|
require = 1,
|
||||||
|
allow = 1)
|
||||||
|
private static ItemStack mctune$trial(
|
||||||
|
IItemHandler handler,
|
||||||
|
int slot,
|
||||||
|
ItemStack stack,
|
||||||
|
boolean simulate,
|
||||||
|
IItemHandler caller,
|
||||||
|
@Coerce Object info,
|
||||||
|
ItemStack input,
|
||||||
|
int count,
|
||||||
|
boolean inFlight) {
|
||||||
|
return ((Prediction) info).mctune$simulate(handler, slot, stack, simulate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ModifyVariable(
|
||||||
|
method = "simulateInsert",
|
||||||
|
at = @At(value = "LOAD", ordinal = 0),
|
||||||
|
ordinal = 2,
|
||||||
|
require = 1,
|
||||||
|
allow = 1)
|
||||||
|
private static int mctune$next(
|
||||||
|
int slot,
|
||||||
|
IItemHandler handler,
|
||||||
|
@Coerce Object info,
|
||||||
|
ItemStack stack,
|
||||||
|
int count,
|
||||||
|
boolean inFlight) {
|
||||||
|
return ((Prediction) info).mctune$next(slot, stack, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = "simulateInsert",
|
||||||
|
at =
|
||||||
|
@At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target =
|
||||||
|
"Lnet/neoforged/neoforge/items/IItemHandler;isItemValid(ILnet/minecraft/world/item/ItemStack;)Z",
|
||||||
|
remap = false),
|
||||||
|
require = 1,
|
||||||
|
allow = 1)
|
||||||
|
private static boolean mctune$valid(
|
||||||
|
IItemHandler handler,
|
||||||
|
int slot,
|
||||||
|
ItemStack stack,
|
||||||
|
IItemHandler caller,
|
||||||
|
@Coerce Object info,
|
||||||
|
ItemStack input,
|
||||||
|
int count,
|
||||||
|
boolean inFlight) {
|
||||||
|
return ((Prediction) info).mctune$check(handler, slot, stack, inFlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = "simulateInsert",
|
||||||
|
at =
|
||||||
|
@At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target =
|
||||||
|
"Lmekanism/common/util/InventoryUtils;areItemsStackable(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Z",
|
||||||
|
remap = false),
|
||||||
|
require = 1,
|
||||||
|
allow = 1)
|
||||||
|
private static boolean mctune$matched(
|
||||||
|
ItemStack left,
|
||||||
|
ItemStack right,
|
||||||
|
IItemHandler handler,
|
||||||
|
@Coerce Object info,
|
||||||
|
ItemStack stack,
|
||||||
|
int count,
|
||||||
|
boolean inFlight) {
|
||||||
|
return ((Prediction) info).mctune$matches(left, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/dev/quorate/tune/mixin/NbtMixin.java
Normal file
27
src/main/java/dev/quorate/tune/mixin/NbtMixin.java
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package dev.quorate.tune.mixin;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
|
|
||||||
|
import dev.quorate.tune.TreeCodec;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
|
||||||
|
@Mixin(value = CompoundTag.class, remap = false)
|
||||||
|
public abstract class NbtMixin {
|
||||||
|
@ModifyExpressionValue(
|
||||||
|
method = "<clinit>",
|
||||||
|
at =
|
||||||
|
@At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target =
|
||||||
|
"Lcom/mojang/serialization/Codec;comapFlatMap(Ljava/util/function/Function;Ljava/util/function/Function;)Lcom/mojang/serialization/Codec;"),
|
||||||
|
require = 1,
|
||||||
|
allow = 1)
|
||||||
|
private static Codec<CompoundTag> mctune$codec(Codec<CompoundTag> original) {
|
||||||
|
return new TreeCodec(original);
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/main/java/dev/quorate/tune/mixin/PlanMixin.java
Normal file
64
src/main/java/dev/quorate/tune/mixin/PlanMixin.java
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
package dev.quorate.tune.mixin;
|
||||||
|
|
||||||
|
import com.refinedmods.refinedstorage.api.resource.ResourceKey;
|
||||||
|
|
||||||
|
import dev.quorate.tune.PlanRows;
|
||||||
|
import dev.quorate.tune.PlanShare;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@Mixin(
|
||||||
|
targets = "com.refinedmods.refinedstorage.api.autocrafting.task.MutablePatternPlan",
|
||||||
|
remap = false)
|
||||||
|
public abstract class PlanMixin implements PlanShare {
|
||||||
|
@Shadow @Final private Map<Integer, Map<ResourceKey, Long>> ingredients;
|
||||||
|
@Unique private final PlanRows<ResourceKey> mctune$rows = new PlanRows<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mctune$share() {
|
||||||
|
mctune$rows.share();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = "copy",
|
||||||
|
at = @At(value = "NEW", target = "(Ljava/util/Map;)Ljava/util/LinkedHashMap;"),
|
||||||
|
require = 1,
|
||||||
|
allow = 1)
|
||||||
|
private LinkedHashMap<ResourceKey, Long> mctune$reuse(Map<ResourceKey, Long> source) {
|
||||||
|
return (LinkedHashMap<ResourceKey, Long>) source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "copy", at = @At("RETURN"), require = 1, allow = 1)
|
||||||
|
private void mctune$copy(CallbackInfoReturnable<?> callback) {
|
||||||
|
mctune$rows.share();
|
||||||
|
((PlanShare) callback.getReturnValue()).mctune$share();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = "addUsedIngredient",
|
||||||
|
at =
|
||||||
|
@At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target =
|
||||||
|
"Ljava/util/Map;computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;"),
|
||||||
|
require = 1,
|
||||||
|
allow = 1)
|
||||||
|
private Object mctune$write(
|
||||||
|
Map<Integer, Map<ResourceKey, Long>> rows,
|
||||||
|
Object index,
|
||||||
|
Function<? super Integer, ? extends Map<ResourceKey, Long>> factory) {
|
||||||
|
mctune$rows.prepare(ingredients);
|
||||||
|
return rows.computeIfAbsent((Integer) index, factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/main/java/dev/quorate/tune/mixin/SetupMixin.java
Normal file
24
src/main/java/dev/quorate/tune/mixin/SetupMixin.java
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
package dev.quorate.tune.mixin;
|
||||||
|
|
||||||
|
import com.buuz135.functionalstorage.util.CompactingUtil;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mixin(
|
||||||
|
targets = "com.buuz135.functionalstorage.inventory.CompactingInventoryHandler",
|
||||||
|
remap = false)
|
||||||
|
public abstract class SetupMixin {
|
||||||
|
@Shadow private List<CompactingUtil.Result> resultList;
|
||||||
|
|
||||||
|
@Overwrite
|
||||||
|
public boolean isSetup() {
|
||||||
|
for (int index = 0; index < resultList.size(); index++) {
|
||||||
|
if (!resultList.get(index).getResult().isEmpty()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue