Add inventory, crafting and NBT optimizations with parity checks

This commit is contained in:
ms2k 2026-10-17 11:49:23 +09:00
commit da43bf966b
40 changed files with 3132 additions and 0 deletions

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

View 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;
}
}

View 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;
};
}
}

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

View 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;
};
}
}

View 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;
}
}

View file

@ -0,0 +1,5 @@
package dev.quorate.tune;
public interface PlanShare {
void mctune$share();
}

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

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

View 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;
}
}

View 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();
}
}

View file

@ -0,0 +1,6 @@
package dev.quorate.tune;
import net.neoforged.fml.common.Mod;
@Mod("mc_tune")
public final class TuneMod {}

View 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;
}
}

View 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;
}
}

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

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

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

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

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

View 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