From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Tue, 11 Jan 2022 18:07:35 -0500 Subject: [PATCH] hydrogen Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 You can find the original code on https://github.com/CaffeineMC/hydrogen-fabric (Yarn mappings) This code specifically was taken from MeeniMc's PR implementing 1.18.x support here: https://github.com/CaffeineMC/hydrogen-fabric/pull/60 diff --git a/src/main/java/com/google/common/collect/HydrogenEntrySet.java b/src/main/java/com/google/common/collect/HydrogenEntrySet.java new file mode 100644 index 0000000000000000000000000000000000000000..290ace0b7c8c6836fa61babf53aca5f2ae0959c1 --- /dev/null +++ b/src/main/java/com/google/common/collect/HydrogenEntrySet.java @@ -0,0 +1,36 @@ +package com.google.common.collect; + +import java.util.Map; + +class HydrogenEntrySet extends ImmutableSet> { + private final K[] key; + private final V[] value; + + private final int size; + + HydrogenEntrySet(K[] key, V[] value, int size) { + this.key = key; + this.value = value; + this.size = size; + } + + @Override + public UnmodifiableIterator> iterator() { + return new HydrogenEntrySetIterator<>(this.key, this.value, this.size); + } + + @Override + public boolean contains(Object object) { + return false; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int size() { + return this.size; + } +} diff --git a/src/main/java/com/google/common/collect/HydrogenEntrySetIterator.java b/src/main/java/com/google/common/collect/HydrogenEntrySetIterator.java new file mode 100644 index 0000000000000000000000000000000000000000..4346ae9da9fe01f89ed77dba37236d068e720aea --- /dev/null +++ b/src/main/java/com/google/common/collect/HydrogenEntrySetIterator.java @@ -0,0 +1,41 @@ +package com.google.common.collect; + +import java.util.Map; + +class HydrogenEntrySetIterator extends UnmodifiableIterator> { + private final K[] key; + private final V[] value; + + private int remaining; + private int idx; + + public HydrogenEntrySetIterator(K[] key, V[] value, int remaining) { + this.remaining = remaining; + this.key = key; + this.value = value; + } + + @Override + public boolean hasNext() { + return this.remaining > 0; + } + + @Override + public Map.Entry next() { + this.skipEmpty(); + + Map.Entry entry = new HydrogenImmutableMapEntry<>(this.key[this.idx], + this.value[this.idx]); + + this.remaining--; + this.idx++; + + return entry; + } + + private void skipEmpty() { + while (this.key[this.idx] == null) { + this.idx++; + } + } +} diff --git a/src/main/java/com/google/common/collect/HydrogenImmutableMapEntry.java b/src/main/java/com/google/common/collect/HydrogenImmutableMapEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..c74ce1d2f8dfc6e50d2ffba462193093c7633c6c --- /dev/null +++ b/src/main/java/com/google/common/collect/HydrogenImmutableMapEntry.java @@ -0,0 +1,28 @@ +package com.google.common.collect; + +import java.util.Map; + +public class HydrogenImmutableMapEntry implements Map.Entry { + private final K key; + private final V value; + + public HydrogenImmutableMapEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return this.key; + } + + @Override + public V getValue() { + return this.value; + } + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/google/common/collect/HydrogenImmutableReferenceHashMap.java b/src/main/java/com/google/common/collect/HydrogenImmutableReferenceHashMap.java new file mode 100644 index 0000000000000000000000000000000000000000..7772a4218fbc76a3f055881aa5301624b6961fe6 --- /dev/null +++ b/src/main/java/com/google/common/collect/HydrogenImmutableReferenceHashMap.java @@ -0,0 +1,134 @@ +package com.google.common.collect; + +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.HashCommon; + +import java.util.Map; + +import static it.unimi.dsi.fastutil.HashCommon.arraySize; + +@SuppressWarnings("unused") +public class HydrogenImmutableReferenceHashMap extends ImmutableMap { + protected transient K[] key; + protected transient V[] value; + protected transient int mask; + protected transient int size; + + public HydrogenImmutableReferenceHashMap() { + + } + + public HydrogenImmutableReferenceHashMap(Map map) { + this(map.size(), Hash.DEFAULT_LOAD_FACTOR); + + for (Map.Entry entry : map.entrySet()) { + this.putInternal(entry.getKey(), entry.getValue()); + } + } + + @SuppressWarnings("unchecked") + private HydrogenImmutableReferenceHashMap(final int size, final float loadFactor) { + if (loadFactor <= 0 || loadFactor > 1) { + throw new IllegalArgumentException("Load factor must be greater than 0 and smaller than or equal to 1"); + } + + if (size < 0) { + throw new IllegalArgumentException("The expected number of elements must be nonnegative"); + } + + int n = arraySize(size, loadFactor); + + this.key = (K[]) new Object[n]; + this.value = (V[]) new Object[n]; + this.mask = n - 1; + this.size = size; + } + + @Override + public int size() { + return this.size; + } + + @Override + public V get(final Object k) { + int pos = HashCommon.mix(System.identityHashCode(k)) & this.mask; + K curr = this.key[pos]; + + // The starting point. + if (curr == null) { + return null; + } else if (k == curr) { + return this.value[pos]; + } + + // There's always an unused entry. + while (true) { + pos = pos + 1 & this.mask; + curr = this.key[pos]; + + if (curr == null) { + return null; + } else if (k == curr) { + return this.value[pos]; + } + } + } + + private void putInternal(final K k, final V v) { + final int pos = this.find(k); + + if (pos < 0) { + int n = -pos - 1; + + this.key[n] = k; + this.value[n] = v; + } else { + this.value[pos] = v; + } + } + + private int find(final K k) { + int pos = HashCommon.mix(System.identityHashCode(k)) & this.mask; + K curr = this.key[pos]; + + // The starting point. + if (curr == null) { + return -(pos + 1); + } else if (k == curr) { + return pos; + } + + // There's always an unused entry. + while (true) { + pos = pos + 1 & this.mask; + curr = this.key[pos]; + + if (curr == null) { + return -(pos + 1); + } else if (k == curr) { + return pos; + } + } + } + + @Override + ImmutableSet> createEntrySet() { + return new HydrogenEntrySet<>(this.key, this.value, this.size); + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet<>(this); + } + + @Override + ImmutableCollection createValues() { + return new ImmutableMapValues<>(this); + } + + @Override + boolean isPartialView() { + return false; + } + +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/cache/StatePropertyTableCache.java b/src/main/java/me/jellysquid/mods/hydrogen/common/cache/StatePropertyTableCache.java new file mode 100644 index 0000000000000000000000000000000000000000..68c69bd90f5ecf3b72d631faac21f89b88fe37cb --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/cache/StatePropertyTableCache.java @@ -0,0 +1,38 @@ +package me.jellysquid.mods.hydrogen.common.cache; + +import me.jellysquid.mods.hydrogen.common.collections.FastImmutableTableCache; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; + +/** + * Many of the column and row key arrays in block state tables will be duplicated, leading to an unnecessary waste of + * memory. Since we have very limited options for trying to construct more optimized table types without throwing + * maintainability or mod compatibility out the window, this class acts as a dirty way to find and de-duplicate arrays + * after we construct our table types. + * + * While this global cache does not provide the ability to remove or clear entries from it, the reality is that it + * shouldn't matter because block state tables are only initialized once and remain loaded for the entire lifetime of + * the game. Even in the event of classloader pre-boot shenanigans, we still shouldn't leak memory as our cache will be + * dropped along with the rest of the loaded classes when the class loader is reaped. + */ +public class StatePropertyTableCache { + public static final FastImmutableTableCache, Comparable, BlockState> BLOCK_STATE_TABLE = + new FastImmutableTableCache<>(); + + public static final FastImmutableTableCache, Comparable, FluidState> FLUID_STATE_TABLE = + new FastImmutableTableCache<>(); + + @SuppressWarnings("unchecked") + public static FastImmutableTableCache, Comparable, S> getTableCache(O owner) { + if (owner instanceof Block) { + return (FastImmutableTableCache, Comparable, S>) BLOCK_STATE_TABLE; + } else if (owner instanceof Fluid) { + return (FastImmutableTableCache, Comparable, S>) FLUID_STATE_TABLE; + } else { + throw new IllegalArgumentException(""); + } + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/collections/CollectionHelper.java b/src/main/java/me/jellysquid/mods/hydrogen/common/collections/CollectionHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..1f4dd657d1cb7c7dcf4bf4b9e213548adb1bae33 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/collections/CollectionHelper.java @@ -0,0 +1,16 @@ +package me.jellysquid.mods.hydrogen.common.collections; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +public class CollectionHelper { + public static List fixed(List src) { + return new FixedArrayList<>(src); + } + + public static Collector> toSizedList(int size) { + return Collectors.toCollection(() -> new ArrayList<>(size)); + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/collections/FastImmutableTable.java b/src/main/java/me/jellysquid/mods/hydrogen/common/collections/FastImmutableTable.java new file mode 100644 index 0000000000000000000000000000000000000000..f87f4d1452dc65dc621493d6dcfaa678bf46069e --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/collections/FastImmutableTable.java @@ -0,0 +1,227 @@ +package me.jellysquid.mods.hydrogen.common.collections; + +import com.google.common.collect.Table; +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.HashCommon; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import static it.unimi.dsi.fastutil.HashCommon.arraySize; + +public class FastImmutableTable implements Table { + private R[] rowKeys; + private int[] rowIndices; + private final int rowMask; + private final int rowCount; + + private C[] colKeys; + private int[] colIndices; + private final int colMask; + private final int colCount; + + private V[] values; + private final int size; + + @SuppressWarnings("unchecked") + public FastImmutableTable(Table table, FastImmutableTableCache cache) { + if (cache == null) { + throw new IllegalArgumentException("Cache must not be null"); + } + + float loadFactor = Hash.DEFAULT_LOAD_FACTOR; + + Set rowKeySet = table.rowKeySet(); + Set colKeySet = table.columnKeySet(); + + this.rowCount = rowKeySet.size(); + this.colCount = colKeySet.size(); + + int rowN = arraySize(this.rowCount, loadFactor); + int colN = arraySize(this.colCount, loadFactor); + + this.rowMask = rowN - 1; + this.rowKeys = (R[]) new Object[rowN]; + this.rowIndices = new int[rowN]; + + this.colMask = colN - 1; + this.colKeys = (C[]) new Object[colN]; + this.colIndices = new int[colN]; + + this.createIndex(this.colKeys, this.colIndices, this.colMask, colKeySet); + this.createIndex(this.rowKeys, this.rowIndices, this.rowMask, rowKeySet); + + this.values = (V[]) new Object[this.rowCount * this.colCount]; + + for (Cell cell : table.cellSet()) { + int colIdx = this.getIndex(this.colKeys, this.colIndices, this.colMask, cell.getColumnKey()); + int rowIdx = this.getIndex(this.rowKeys, this.rowIndices, this.rowMask, cell.getRowKey()); + + if (colIdx < 0 || rowIdx < 0) { + throw new IllegalStateException("Missing index for " + cell); + } + + this.values[this.colCount * rowIdx + colIdx] = cell.getValue(); + } + + this.size = table.size(); + + this.rowKeys = cache.dedupRows(this.rowKeys); + this.rowIndices = cache.dedupIndices(this.rowIndices); + + this.colIndices = cache.dedupIndices(this.colIndices); + this.colKeys = cache.dedupColumns(this.colKeys); + + this.values = cache.dedupValues(this.values); + } + + private void createIndex(T[] keys, int[] indices, int mask, Collection iterable) { + int index = 0; + + for (T obj : iterable) { + int i = this.find(keys, mask, obj); + + if (i < 0) { + int pos = -i - 1; + + keys[pos] = obj; + indices[pos] = index++; + } + } + } + + private int getIndex(T[] keys, int[] indices, int mask, T key) { + int pos = this.find(keys, mask, key); + + if (pos < 0) { + return -1; + } + + return indices[pos]; + } + + @Override + public boolean contains(Object rowKey, Object columnKey) { + return this.get(rowKey, columnKey) != null; + } + + @Override + public boolean containsRow(Object rowKey) { + return this.find(this.rowKeys, this.rowMask, rowKey) >= 0; + } + + @Override + public boolean containsColumn(Object columnKey) { + return this.find(this.colKeys, this.colMask, columnKey) >= 0; + } + + @Override + public boolean containsValue(Object value) { + return ArrayUtils.contains(this.values, value); + } + + @Override + public V get(Object rowKey, Object columnKey) { + final int row = this.getIndex(this.rowKeys, this.rowIndices, this.rowMask, rowKey); + final int col = this.getIndex(this.colKeys, this.colIndices, this.colMask, columnKey); + + if (row < 0 || col < 0) { + return null; + } + + return this.values[this.colCount * row + col]; + } + + @Override + public boolean isEmpty() { + return this.size() == 0; + } + + @Override + public int size() { + return this.size; + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public V put(R rowKey, C columnKey, V val) { + throw new UnsupportedOperationException(); + } + + private int find(T[] key, int mask, T value) { + T curr; + int pos; + // The starting point. + if ((curr = key[pos = HashCommon.mix(value.hashCode()) & mask]) == null) { + return -(pos + 1); + } + if (value.equals(curr)) { + return pos; + } + // There's always an unused entry. + while (true) { + if ((curr = key[pos = pos + 1 & mask]) == null) { + return -(pos + 1); + } + if (value.equals(curr)) { + return pos; + } + } + } + + @Override + public void putAll(Table table) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(Object rowKey, Object columnKey) { + throw new UnsupportedOperationException(); + } + + @Override + public Map row(R rowKey) { + throw new UnsupportedOperationException(); + } + + @Override + public Map column(C columnKey) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> cellSet() { + throw new UnsupportedOperationException(); + } + + @Override + public Set rowKeySet() { + throw new UnsupportedOperationException(); + } + + @Override + public Set columnKeySet() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + throw new UnsupportedOperationException(); + } + + @Override + public Map> rowMap() { + throw new UnsupportedOperationException(); + } + + @Override + public Map> columnMap() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/collections/FastImmutableTableCache.java b/src/main/java/me/jellysquid/mods/hydrogen/common/collections/FastImmutableTableCache.java new file mode 100644 index 0000000000000000000000000000000000000000..8fd17d2738be1cacedf56bb5c22ead9e473a9b99 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/collections/FastImmutableTableCache.java @@ -0,0 +1,44 @@ +package me.jellysquid.mods.hydrogen.common.collections; + +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.ints.IntArrays; +import it.unimi.dsi.fastutil.objects.ObjectArrays; +import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; + +/** + * @param The type used by the + * @param + * @param + */ +public class FastImmutableTableCache { + private final ObjectOpenCustomHashSet rows; + private final ObjectOpenCustomHashSet columns; + private final ObjectOpenCustomHashSet values; + + private final ObjectOpenCustomHashSet indices; + + @SuppressWarnings("unchecked") + public FastImmutableTableCache() { + this.rows = new ObjectOpenCustomHashSet<>((Hash.Strategy) ObjectArrays.HASH_STRATEGY); + this.columns = new ObjectOpenCustomHashSet<>((Hash.Strategy) ObjectArrays.HASH_STRATEGY); + this.values = new ObjectOpenCustomHashSet<>((Hash.Strategy) ObjectArrays.HASH_STRATEGY); + + this.indices = new ObjectOpenCustomHashSet<>(IntArrays.HASH_STRATEGY); + } + + public synchronized V[] dedupValues(V[] values) { + return this.values.addOrGet(values); + } + + public synchronized R[] dedupRows(R[] rows) { + return this.rows.addOrGet(rows); + } + + public synchronized C[] dedupColumns(C[] columns) { + return this.columns.addOrGet(columns); + } + + public synchronized int[] dedupIndices(int[] ints) { + return this.indices.addOrGet(ints); + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/collections/FixedArrayList.java b/src/main/java/me/jellysquid/mods/hydrogen/common/collections/FixedArrayList.java new file mode 100644 index 0000000000000000000000000000000000000000..35e4c91e9b87d7fc8394b046c85b2b7f75afeb27 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/collections/FixedArrayList.java @@ -0,0 +1,153 @@ +package me.jellysquid.mods.hydrogen.common.collections; + +import com.google.common.collect.Iterators; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.*; + +public class FixedArrayList implements List { + private final T[] array; + + @SuppressWarnings("unchecked") + public FixedArrayList(List list) { + this(list.toArray((T[]) new Object[0])); + } + + public FixedArrayList(T[] array) { + this.array = array; + } + + @Override + public int size() { + return this.array.length; + } + + @Override + public boolean isEmpty() { + return this.array.length == 0; + } + + @Override + public boolean contains(Object o) { + return ArrayUtils.contains(this.array, o); + } + + @Override + public Iterator iterator() { + return Iterators.forArray(this.array); + } + + @Override + public Object[] toArray() { + return this.array.clone(); + } + + @SuppressWarnings("unchecked") + @Override + public T1[] toArray(T1[] dst) { + T[] src = this.array; + + if (dst.length < src.length) { + return (T1[]) Arrays.copyOf(src, src.length, dst.getClass()); + } + + System.arraycopy(src, 0, dst, 0, src.length); + + if (dst.length > src.length) { + dst[src.length] = null; + } + + return dst; + } + + @Override + public boolean add(T t) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) { + if (!ArrayUtils.contains(this.array, o)) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public T get(int index) { + return this.array[index]; + } + + @Override + public T set(int index, T element) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(int index, T element) { + throw new UnsupportedOperationException(); + } + + @Override + public T remove(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int indexOf(Object o) { + return ArrayUtils.indexOf(this.array, o); + } + + @Override + public int lastIndexOf(Object o) { + return ArrayUtils.lastIndexOf(this.array, o); + } + + @Override + public ListIterator listIterator() { + throw new UnsupportedOperationException(); + } + + @Override + public ListIterator listIterator(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public List subList(int fromIndex, int toIndex) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/dedup/DeduplicationCache.java b/src/main/java/me/jellysquid/mods/hydrogen/common/dedup/DeduplicationCache.java new file mode 100644 index 0000000000000000000000000000000000000000..c4ebb3c59f2ddb63f39d0fdea62f41a042b68c25 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/dedup/DeduplicationCache.java @@ -0,0 +1,56 @@ +package me.jellysquid.mods.hydrogen.common.dedup; + +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; + +import java.util.Objects; + +public class DeduplicationCache { + private final ObjectOpenCustomHashSet pool; + + private int attemptedInsertions = 0; + private int deduplicated = 0; + + public DeduplicationCache(Hash.Strategy strategy) { + this.pool = new ObjectOpenCustomHashSet<>(strategy); + } + + public DeduplicationCache() { + this.pool = new ObjectOpenCustomHashSet<>(new Hash.Strategy() { + @Override + public int hashCode(T o) { + return Objects.hashCode(o); + } + + @Override + public boolean equals(T a, T b) { + return Objects.equals(a, b); + } + }); + } + + public synchronized T deduplicate(T item) { + this.attemptedInsertions++; + + T result = this.pool.addOrGet(item); + + if (result != item) { + this.deduplicated++; + } + + return result; + } + + public synchronized void clearCache() { + this.attemptedInsertions = 0; + this.deduplicated = 0; + + this.pool.clear(); + } + + @Override + public synchronized String toString() { + return String.format("DeduplicationCache ( %d/%d de-duplicated, %d pooled )", + this.deduplicated, this.attemptedInsertions, this.pool.size()); + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/dedup/IdentifierCaches.java b/src/main/java/me/jellysquid/mods/hydrogen/common/dedup/IdentifierCaches.java new file mode 100644 index 0000000000000000000000000000000000000000..69246b3e2944c273818c4ef4731d6b2f2e6d18fc --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/dedup/IdentifierCaches.java @@ -0,0 +1,6 @@ +package me.jellysquid.mods.hydrogen.common.dedup; + +public class IdentifierCaches { + public static final DeduplicationCache NAMESPACES = new DeduplicationCache<>(); + public static final DeduplicationCache PATH = new DeduplicationCache<>(); +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/jvm/ClassConstructors.java b/src/main/java/me/jellysquid/mods/hydrogen/common/jvm/ClassConstructors.java new file mode 100644 index 0000000000000000000000000000000000000000..7b3a7c6e2ce171b5ab7444e16e75c1a53ff3b899 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/jvm/ClassConstructors.java @@ -0,0 +1,44 @@ +package me.jellysquid.mods.hydrogen.common.jvm; + +import com.google.common.collect.ImmutableMap; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.util.Map; + +@SuppressWarnings("unchecked") +public class ClassConstructors { + private static MethodHandle FAST_IMMUTABLE_REFERENCE_HASH_MAP_CONSTRUCTOR; + + public static void init() { + initGuavaExtensions(); + } + + private static void initGuavaExtensions() { // Note: maybe this isn't needed as this is no longer a mod, need to look into this further. + // define classes in their reverse link order to prevent duplicate definition issues + ClassDefineTool.defineClass(ImmutableMap.class, "com.google.common.collect.HydrogenImmutableMapEntry"); + ClassDefineTool.defineClass(ImmutableMap.class, "com.google.common.collect.HydrogenEntrySetIterator"); + ClassDefineTool.defineClass(ImmutableMap.class, "com.google.common.collect.HydrogenEntrySet"); + + Class immutableRefHashMapClass = ClassDefineTool.defineClass(ImmutableMap.class, "com.google.common.collect.HydrogenImmutableReferenceHashMap"); + + try { + FAST_IMMUTABLE_REFERENCE_HASH_MAP_CONSTRUCTOR = MethodHandles.lookup() + .findConstructor(immutableRefHashMapClass, MethodType.methodType(Void.TYPE, Map.class)) + // compiler can only generate a desc returning ImmutableMap below + .asType(MethodType.methodType(ImmutableMap.class, Map.class)); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to find constructor", e); + } + } + + public static ImmutableMap createFastImmutableMap(Map orig) { + try { + return (ImmutableMap) FAST_IMMUTABLE_REFERENCE_HASH_MAP_CONSTRUCTOR.invokeExact((Map) orig); + } catch (Throwable e) { + throw new RuntimeException("Could not instantiate collection", e); + } + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/jvm/ClassDefineTool.java b/src/main/java/me/jellysquid/mods/hydrogen/common/jvm/ClassDefineTool.java new file mode 100644 index 0000000000000000000000000000000000000000..db7d91dfb8b852e1c204c44f62847f48261371f7 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/jvm/ClassDefineTool.java @@ -0,0 +1,44 @@ +package me.jellysquid.mods.hydrogen.common.jvm; + +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.net.URL; + +public class ClassDefineTool { + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private static final Logger LOGGER = LogManager.getLogger("Hydrogen"); + + public static Class defineClass(Class context, String name) { + String path = "/" + name.replace('.', '/') + ".class"; + URL url = ClassDefineTool.class.getResource(path); + + if (url == null) { + throw new RuntimeException("Couldn't find resource: " + path); + } + + LOGGER.info("Injecting class '{}' (url: {})", name, url); + + byte[] code; + + try { + code = IOUtils.toByteArray(url); + } catch (IOException e) { + throw new RuntimeException("Could not read class bytes from resources: " + path, e); + } + + try { + // The context class need to be in a module that exports and opens to ClassDefineTool + // Example: guava's automatic module exports and opens to everything + MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(context, LOOKUP); + return privateLookup.defineClass(code); + } catch (Throwable throwable) { + throw new RuntimeException("Failed to define class", throwable); + } + } + +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneBoolean.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneBoolean.java new file mode 100644 index 0000000000000000000000000000000000000000..55844de0ca4247c165a49b307f66d0d141134b2a --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneBoolean.java @@ -0,0 +1,46 @@ +package me.jellysquid.mods.hydrogen.common.state.all; + +import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchOne; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import java.util.List; +import java.util.function.Predicate; + +public class AllMatchOneBoolean implements Predicate { + private final Property[] properties; + private final boolean[] values; + + public AllMatchOneBoolean(List> list) { + int size = list.size(); + + this.properties = new Property[size]; + this.values = new boolean[size]; + + for (int i = 0; i < size; i++) { + SingleMatchOne predicate = (SingleMatchOne) list.get(i); + + this.properties[i] = predicate.property; + this.values[i] = (boolean) predicate.value; + } + } + + public static boolean canReplace(List> list) { + return list.stream() + .allMatch(p -> { + return p instanceof SingleMatchOne && ((SingleMatchOne) p).value instanceof Boolean; + }); + } + + @Override + public boolean test(BlockState blockState) { + for (int i = 0; i < this.properties.length; i++) { + Boolean value = (Boolean) blockState.getValue(this.properties[i]); + + if (value != this.values[i]) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneObject.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneObject.java new file mode 100644 index 0000000000000000000000000000000000000000..49be1c9d9da46dee2a14a5423d25a09ccfb19e3a --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/all/AllMatchOneObject.java @@ -0,0 +1,37 @@ +package me.jellysquid.mods.hydrogen.common.state.all; + +import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchOne; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import java.util.List; +import java.util.function.Predicate; + +public class AllMatchOneObject implements Predicate { + private final Property[] properties; + private final Object[] values; + + public AllMatchOneObject(List> list) { + int size = list.size(); + + this.properties = new Property[size]; + this.values = new Object[size]; + + for (int i = 0; i < size; i++) { + SingleMatchOne predicate = (SingleMatchOne) list.get(i); + + this.properties[i] = predicate.property; + this.values[i] = predicate.value; + } + } + + @Override + public boolean test(BlockState blockState) { + for (int i = 0; i < this.properties.length; i++) { + if (blockState.getValue(this.properties[i]) != this.values[i]) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/any/AllMatchAnyObject.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/any/AllMatchAnyObject.java new file mode 100644 index 0000000000000000000000000000000000000000..b46cdb3fd8131d3ce570ed9fadae2c790645eba6 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/any/AllMatchAnyObject.java @@ -0,0 +1,39 @@ +package me.jellysquid.mods.hydrogen.common.state.any; + +import me.jellysquid.mods.hydrogen.common.state.single.SingleMatchAny; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.List; +import java.util.function.Predicate; + +public class AllMatchAnyObject implements Predicate { + private final Property[] properties; + private final Object[][] values; + + public AllMatchAnyObject(List> list) { + int size = list.size(); + + this.properties = new Property[size]; + this.values = new Object[size][]; + + for (int i = 0; i < size; i++) { + SingleMatchAny predicate = (SingleMatchAny) list.get(i); + + this.properties[i] = predicate.property; + this.values[i] = predicate.values; + } + } + + @Override + public boolean test(BlockState blockState) { + for (int i = 0; i < this.properties.length; i++) { + if (!ArrayUtils.contains(this.values[i], blockState.getValue(this.properties[i]))) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchAny.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchAny.java new file mode 100644 index 0000000000000000000000000000000000000000..5ba47d431bb53cb1ccb32cff4663d26466b53a33 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchAny.java @@ -0,0 +1,63 @@ +package me.jellysquid.mods.hydrogen.common.state.single; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; + +public class SingleMatchAny implements Predicate { + public static final ObjectOpenHashSet PREDICATES = new ObjectOpenHashSet<>(); + + public final Property property; + public final Object[] values; + + private SingleMatchAny(Property property, List values) { + this.property = property; + this.values = values.toArray(); + } + + public static SingleMatchAny create(Property property, List values) { + return PREDICATES.addOrGet(new SingleMatchAny(property, values)); + } + + public static boolean areOfType(List> predicates) { + return predicates.stream() + .allMatch(p -> { + return p instanceof SingleMatchAny; + }); + } + + public static boolean valuesMatchType(List> predicates, Class type) { + return predicates.stream() + .allMatch(p -> { + return p instanceof SingleMatchAny && + Arrays.stream(((SingleMatchAny) p).values).allMatch(t -> type.isInstance(p)); + }); + } + + @Override + public boolean test(BlockState blockState) { + return ArrayUtils.contains(this.values, blockState.getValue(this.property)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SingleMatchAny that = (SingleMatchAny) o; + return Objects.equals(property, that.property) && + Arrays.equals(values, that.values); + } + + @Override + public int hashCode() { + int result = Objects.hash(property); + result = 31 * result + Arrays.hashCode(values); + return result; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchOne.java b/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchOne.java new file mode 100644 index 0000000000000000000000000000000000000000..18649ef152ea6f7ae08bfa217fd1a1ad6581245e --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/state/single/SingleMatchOne.java @@ -0,0 +1,35 @@ +package me.jellysquid.mods.hydrogen.common.state.single; + +import java.util.List; +import java.util.function.Predicate; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; + +public class SingleMatchOne implements Predicate { + public final Property property; + public final Object value; + + public SingleMatchOne(Property property, Object value) { + this.property = property; + this.value = value; + } + + public static boolean areOfType(List> predicates) { + return predicates.stream() + .allMatch(p -> { + return p instanceof SingleMatchOne; + }); + } + + public static boolean valuesMatchType(List> predicates, Class type) { + return predicates.stream() + .allMatch(p -> { + return p instanceof SingleMatchOne && type.isInstance(((SingleMatchOne) p).value); + }); + } + + @Override + public boolean test(BlockState blockState) { + return blockState.getValue(this.property) == this.value; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/util/AllPredicate.java b/src/main/java/me/jellysquid/mods/hydrogen/common/util/AllPredicate.java new file mode 100644 index 0000000000000000000000000000000000000000..a4836a1a6616072b2cc5cc78a7b4902ca92f99b6 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/util/AllPredicate.java @@ -0,0 +1,22 @@ +package me.jellysquid.mods.hydrogen.common.util; + +import java.util.function.Predicate; + +public class AllPredicate implements Predicate { + private final Predicate[] predicates; + + public AllPredicate(Predicate[] predicates) { + this.predicates = predicates; + } + + @Override + public boolean test(T t) { + for (Predicate predicate : this.predicates) { + if (!predicate.test(t)) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/me/jellysquid/mods/hydrogen/common/util/AnyPredicate.java b/src/main/java/me/jellysquid/mods/hydrogen/common/util/AnyPredicate.java new file mode 100644 index 0000000000000000000000000000000000000000..6877466f27fe8a22e2a68cf954dfaf313f4db94e --- /dev/null +++ b/src/main/java/me/jellysquid/mods/hydrogen/common/util/AnyPredicate.java @@ -0,0 +1,22 @@ +package me.jellysquid.mods.hydrogen.common.util; + +import java.util.function.Predicate; + +public class AnyPredicate implements Predicate { + private final Predicate[] predicates; + + public AnyPredicate(Predicate[] predicates) { + this.predicates = predicates; + } + + @Override + public boolean test(T t) { + for (Predicate predicate : this.predicates) { + if (predicate.test(t)) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java index 6e5d13e63c97cb95b93af1d997dc0eb53f966566..231448a1a49f1c4034ce09e06dcbdabe103bd8dd 100644 --- a/src/main/java/net/minecraft/resources/ResourceLocation.java +++ b/src/main/java/net/minecraft/resources/ResourceLocation.java @@ -18,6 +18,7 @@ import net.minecraft.ResourceLocationException; import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.util.GsonHelper; import org.apache.commons.lang3.StringUtils; +import me.jellysquid.mods.hydrogen.common.dedup.IdentifierCaches; // JettPack public class ResourceLocation implements Comparable { public static final Codec CODEC = Codec.STRING.comapFlatMap(ResourceLocation::read, ResourceLocation::toString).stable(); @@ -29,8 +30,10 @@ public class ResourceLocation implements Comparable { protected final String path; protected ResourceLocation(String[] id) { - this.namespace = StringUtils.isEmpty(id[0]) ? "minecraft" : id[0]; - this.path = id[1]; + // JettPack start - port Hydrogen + this.namespace = IdentifierCaches.NAMESPACES.deduplicate(StringUtils.isEmpty(id[0]) ? "minecraft" : id[0]); + this.path = IdentifierCaches.PATH.deduplicate(id[1]); + // JettPack end if (!isValidNamespace(this.namespace)) { throw new ResourceLocationException("Non [a-z0-9_.-] character in namespace of location: " + org.apache.commons.lang3.StringUtils.normalizeSpace(this.namespace) + ":" + org.apache.commons.lang3.StringUtils.normalizeSpace(this.path)); // Paper } else if (!isValidPath(this.path)) { diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java index 2f5c021b6849acb81064d55418707921424b649b..8cf835307c36fb048d62af9f417daeeeb71a0cff 100644 --- a/src/main/java/net/minecraft/server/Main.java +++ b/src/main/java/net/minecraft/server/Main.java @@ -64,6 +64,7 @@ public class Main { @DontObfuscate public static void main(final OptionSet optionset) { // CraftBukkit - replaces main(String[] astring) + me.jellysquid.mods.hydrogen.common.jvm.ClassConstructors.init(); // JettPack SharedConstants.tryDetectVersion(); /* CraftBukkit start - Replace everything OptionParser optionparser = new OptionParser(); diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java index 5be5eabc222b9e20c083ff83fae52010b19ea854..c263220bc504bdb2067ac8920fceac6de1bb850e 100644 --- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java +++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java @@ -44,7 +44,7 @@ public abstract class StateHolder { protected StateHolder(O owner, ImmutableMap, Comparable> entries, MapCodec codec) { this.owner = owner; - this.values = entries; + this.values = me.jellysquid.mods.hydrogen.common.jvm.ClassConstructors.createFastImmutableMap(entries); // JettPack - port hydrogen this.propertiesCodec = codec; this.optimisedTable = new io.papermc.paper.util.table.ZeroCollidingReferenceStateTable(this, entries); // Paper - optimise state lookup }