From 69b80568e9a6716bbbdca69c3e8d2f6fc6b34f2e Mon Sep 17 00:00:00 2001 From: Aikar Date: Sat, 15 Sep 2018 13:22:14 -0400 Subject: [PATCH] Concurrency and Performance Improvements to DataFixers Found some more unsafe operations in DataFixers. Also replaced quite a few bad uses of Map.containsKey containsKey is a common newbie mistake that "reads" cleaner, but results in double the performance cost of all map operations as containsKey in MOST cases where null values are not used is identical to get() == null Considering how deep datafixers go in call stacks, with tons of map lookups, this micro optimization could provide some gains. Additionally, many of the containsKey/get/put style operations were also a concurrency risk, resulting in multiple computation/insertions. --- ...and-performance-issues-in-DataFixers.patch | 339 ++++++++++++++++++ ...Fix-concurrency-issues-in-DataFixers.patch | 144 -------- scripts/importmcdev.sh | 5 + 3 files changed, 344 insertions(+), 144 deletions(-) create mode 100644 Spigot-Server-Patches/Fix-concurrency-and-performance-issues-in-DataFixers.patch delete mode 100644 Spigot-Server-Patches/Fix-concurrency-issues-in-DataFixers.patch diff --git a/Spigot-Server-Patches/Fix-concurrency-and-performance-issues-in-DataFixers.patch b/Spigot-Server-Patches/Fix-concurrency-and-performance-issues-in-DataFixers.patch new file mode 100644 index 0000000000..336e77e351 --- /dev/null +++ b/Spigot-Server-Patches/Fix-concurrency-and-performance-issues-in-DataFixers.patch @@ -0,0 +1,339 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 3 Sep 2018 22:18:38 -0400 +Subject: [PATCH] Fix concurrency and performance issues in DataFixers + +We are seeing issues with DataFixers being not thread safe in async chunks +and even in some spigot packet sending code. + +There are a few more global objects that are mutated that need to +be synchronized to be safe for use over multiple threads. + +There may be more cases, but these are extremely obvious ones. + +Also replaced quite a few bad uses of Map.containsKey + +containsKey is a common newbie mistake that "reads" cleaner, but +results in double the performance cost of all map operations as +containsKey in MOST cases where null values are not used is identical to get() == null + +Considering how deep datafixers go in call stacks, with tons of map lookups, +this micro optimization could provide some gains. + +Additionally, many of the containsKey/get/put style operations were +also a concurrency risk, resulting in multiple computation/insertions. + +diff --git a/src/main/java/com/mojang/datafixers/DataFixerUpper.java b/src/main/java/com/mojang/datafixers/DataFixerUpper.java +index fb2c380f8a..a4922a35a2 100644 +--- a/src/main/java/com/mojang/datafixers/DataFixerUpper.java ++++ b/src/main/java/com/mojang/datafixers/DataFixerUpper.java +@@ -0,0 +0,0 @@ public class DataFixerUpper implements DataFixer { + private final Int2ObjectSortedMap schemas; + private final List globalList; + private final IntSortedSet fixerVersions; +- private final Long2ObjectMap rules = new Long2ObjectOpenHashMap<>(); ++ private final Long2ObjectMap rules = it.unimi.dsi.fastutil.longs.Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); // Paper + + protected DataFixerUpper(final Int2ObjectSortedMap schemas, final List globalList, final IntSortedSet fixerVersions) { + this.schemas = schemas; +@@ -0,0 +0,0 @@ public class DataFixerUpper implements DataFixer { + final int expandedDataVersion = DataFixUtils.makeKey(dataVersion); + + final long key = (long) expandedVersion << 32 | expandedDataVersion; +- if (!rules.containsKey(key)) { ++ return rules.computeIfAbsent(key, k -> { // Paper + final List rules = Lists.newArrayList(); + for (final DataFix fix : globalList) { + final int fixVersion = fix.getVersionKey(); +@@ -0,0 +0,0 @@ public class DataFixerUpper implements DataFixer { + rules.add(fixRule); + } + } +- this.rules.put(key, TypeRewriteRule.seq(rules)); +- } +- return rules.get(key); ++ return TypeRewriteRule.seq(rules); // Paper ++ }); // Paper + } + + protected IntSortedSet fixerVersions() { +diff --git a/src/main/java/com/mojang/datafixers/NamedChoiceFinder.java b/src/main/java/com/mojang/datafixers/NamedChoiceFinder.java +index 2c259d74e9..17481fb6e6 100644 +--- a/src/main/java/com/mojang/datafixers/NamedChoiceFinder.java ++++ b/src/main/java/com/mojang/datafixers/NamedChoiceFinder.java +@@ -0,0 +0,0 @@ final class NamedChoiceFinder implements OpticFinder { + }*/ + if (targetType instanceof TaggedChoice.TaggedChoiceType) { + final TaggedChoice.TaggedChoiceType choiceType = (TaggedChoice.TaggedChoiceType) targetType; +- if (choiceType.types().containsKey(name)) { +- final Type elementType = choiceType.types().get(name); ++ // Paper start - performance - don't use containsKey ++ final Type elementType = choiceType.types().get(name); ++ if (elementType != null) { ++ // Paper end + if (!Objects.equals(type, elementType)) { + return Either.right(new Type.FieldNotFoundException(String.format("Type error for choice type \"%s\": expected type: %s, actual type: %s)", name, targetType, elementType))); + } +diff --git a/src/main/java/com/mojang/datafixers/functions/PointFree.java b/src/main/java/com/mojang/datafixers/functions/PointFree.java +index 0d88490f77..028942b8ea 100644 +--- a/src/main/java/com/mojang/datafixers/functions/PointFree.java ++++ b/src/main/java/com/mojang/datafixers/functions/PointFree.java +@@ -0,0 +0,0 @@ public abstract class PointFree { + private Function, T> value; + + @SuppressWarnings("ConstantConditions") +- public Function, T> evalCached() { ++ public synchronized Function, T> evalCached() { // Paper + if (!initialized) { + initialized = true; + value = eval(); +diff --git a/src/main/java/com/mojang/datafixers/schemas/Schema.java b/src/main/java/com/mojang/datafixers/schemas/Schema.java +index 7c67d989e0..f1fd694b06 100644 +--- a/src/main/java/com/mojang/datafixers/schemas/Schema.java ++++ b/src/main/java/com/mojang/datafixers/schemas/Schema.java +@@ -0,0 +0,0 @@ import java.util.function.Function; + import java.util.function.Supplier; + + public class Schema { +- protected final Object2IntMap RECURSIVE_TYPES = new Object2IntOpenHashMap<>(); +- private final Map> TYPE_TEMPLATES = Maps.newHashMap(); ++ protected final Object2IntMap RECURSIVE_TYPES = it.unimi.dsi.fastutil.objects.Object2IntMaps.synchronize(new Object2IntOpenHashMap<>()); // Paper ++ private final Map> TYPE_TEMPLATES = Maps.newConcurrentMap(); // Paper + private final Map> TYPES; + private final int versionKey; + private final String name; +@@ -0,0 +0,0 @@ public class Schema { + } + + protected Map> buildTypes() { +- final Map> types = Maps.newHashMap(); ++ final Map> types = Maps.newConcurrentMap(); // Paper + + final List templates = Lists.newArrayList(); + ++ synchronized (RECURSIVE_TYPES) { // Paper + for (final Object2IntMap.Entry entry : RECURSIVE_TYPES.object2IntEntrySet()) { + templates.add(DSL.check(entry.getKey(), entry.getIntValue(), getTemplate(entry.getKey()))); +- } ++ } } // Paper + + final TypeTemplate choice = templates.stream().reduce(DSL::or).get(); + final TypeFamily family = new RecursiveTypeFamily(name, choice); + ++ synchronized (TYPE_TEMPLATES) { // Paper + for (final String name : TYPE_TEMPLATES.keySet()) { + final Type type; +- if (RECURSIVE_TYPES.containsKey(name)) { +- type = family.apply(RECURSIVE_TYPES.getInt(name)); ++ // Paper start - concurrency and performance improvement, don't use containsKey ++ int recurseId = RECURSIVE_TYPES.getOrDefault(name, -1); ++ if (recurseId != -1) { ++ type = family.apply(recurseId); ++ // Paper end + } else { + type = getTemplate(name).apply(family).apply(-1); + } + types.put(name, type); +- } ++ } } // Paper + return types; + } + +@@ -0,0 +0,0 @@ public class Schema { + } + + public TypeTemplate id(final String name) { +- if (RECURSIVE_TYPES.containsKey(name)) { +- return DSL.id(RECURSIVE_TYPES.get(name)); ++ // Paper start - improve concurrency and performance ++ int id = RECURSIVE_TYPES.getOrDefault(name, -1); ++ if (id != -1) { ++ return DSL.id(id); ++ // Paper end + } + return getTemplate(name); + } +@@ -0,0 +0,0 @@ public class Schema { + + public Type getChoiceType(final DSL.TypeReference type, final String choiceName) { + final TaggedChoice.TaggedChoiceType choiceType = findChoiceType(type); +- if (!choiceType.types().containsKey(choiceName)) { ++ // Paper start - performance and concurrency - don't use containsKey ++ Type result = choiceType.types().get(choiceName); ++ if (result == null) { + throw new IllegalArgumentException("Data fixer not registered for: " + choiceName + " in " + type.typeName()); + } +- return choiceType.types().get(choiceName); ++ return result; ++ // Paper end + } + + public TaggedChoice.TaggedChoiceType findChoiceType(final DSL.TypeReference type) { +@@ -0,0 +0,0 @@ public class Schema { + public void registerType(final boolean recursive, final DSL.TypeReference type, final Supplier template) { + TYPE_TEMPLATES.put(type.typeName(), template); + // TODO: calculate recursiveness instead of hardcoding ++ synchronized (RECURSIVE_TYPES) { // Paper + if (recursive && !RECURSIVE_TYPES.containsKey(type.typeName())) { + RECURSIVE_TYPES.put(type.typeName(), RECURSIVE_TYPES.size()); +- } ++ } } // Paper + } + + public int getVersionKey() { +diff --git a/src/main/java/com/mojang/datafixers/types/DynamicOps.java b/src/main/java/com/mojang/datafixers/types/DynamicOps.java +index 3c76929f24..f21531b3cd 100644 +--- a/src/main/java/com/mojang/datafixers/types/DynamicOps.java ++++ b/src/main/java/com/mojang/datafixers/types/DynamicOps.java +@@ -0,0 +0,0 @@ public interface DynamicOps { + + default Optional getGeneric(final T input, final T key) { + return getMapValues(input).flatMap(map -> { +- if (map.containsKey(key)) { +- return Optional.of(map.get(key)); +- } +- return Optional.empty(); ++ // Paper start - performance - don't use containsKey ++ T value = map.get(key); ++ return value != null ? Optional.of(value) : Optional.empty(); ++ // Paper end + }); + } + +diff --git a/src/main/java/com/mojang/datafixers/types/Type.java b/src/main/java/com/mojang/datafixers/types/Type.java +index a80e3fee91..2d5bae7a37 100644 +--- a/src/main/java/com/mojang/datafixers/types/Type.java ++++ b/src/main/java/com/mojang/datafixers/types/Type.java +@@ -0,0 +0,0 @@ import javax.annotation.Nullable; + import java.util.Map; + import java.util.Objects; + import java.util.Optional; ++import java.util.concurrent.CompletableFuture; + + public abstract class Type implements App { ++ private static final Map, TypeRewriteRule, PointFreeRule>, CompletableFuture>>> PENDING_REWRITE_CACHE = Maps.newConcurrentMap(); + private static final Map, TypeRewriteRule, PointFreeRule>, Optional>> REWRITE_CACHE = Maps.newConcurrentMap(); + + public static class Mu implements K1 {} +@@ -0,0 +0,0 @@ public abstract class Type implements App { + @SuppressWarnings("unchecked") + public Optional> rewrite(final TypeRewriteRule rule, final PointFreeRule fRule) { + final Triple, TypeRewriteRule, PointFreeRule> key = Triple.of(this, rule, fRule); +- if (!REWRITE_CACHE.containsKey(key)) { +- final Optional> result = rule.rewrite(this).flatMap(r -> r.view().rewrite(fRule).map(view -> RewriteResult.create(view, r.recData()))); ++ // Paper start - concurrency and performance boost - this code under contention would generate multiple rewrites ++ // rewrite this to use a CompletableFuture for pending rewrites. We can not use computeIfAbsent because this is ++ // a recursive call that will block server startup during the Bootstrap phrase thats trying to precache these rewrites ++ Optional> rewrite = REWRITE_CACHE.get(key); ++ //noinspection OptionalAssignedToNull ++ if (rewrite != null) { ++ return (Optional>) rewrite; ++ } ++ CompletableFuture>> pending; ++ boolean needsCreate; ++ synchronized (PENDING_REWRITE_CACHE) { ++ pending = PENDING_REWRITE_CACHE.get(key); ++ needsCreate = pending == null; ++ if (pending == null) { ++ pending = new CompletableFuture<>(); ++ PENDING_REWRITE_CACHE.put(key, pending); ++ } ++ } ++ if (needsCreate) { ++ Optional> result = rule.rewrite(this).flatMap(r -> r.view().rewrite(fRule).map(view -> RewriteResult.create(view, r.recData()))); + REWRITE_CACHE.put(key, result); ++ pending.complete(result); ++ PENDING_REWRITE_CACHE.remove(key); ++ return result; ++ } else { ++ return (Optional>) pending.join(); + } +- return (Optional>) REWRITE_CACHE.get(key); ++ // Paper end + } + + public Type getSetType(final OpticFinder optic, final Type newType) { +diff --git a/src/main/java/com/mojang/datafixers/types/families/RecursiveTypeFamily.java b/src/main/java/com/mojang/datafixers/types/families/RecursiveTypeFamily.java +index 4a1f906837..93c2f565fd 100644 +--- a/src/main/java/com/mojang/datafixers/types/families/RecursiveTypeFamily.java ++++ b/src/main/java/com/mojang/datafixers/types/families/RecursiveTypeFamily.java +@@ -0,0 +0,0 @@ public final class RecursiveTypeFamily implements TypeFamily { + private final TypeTemplate template; + private final int size; + +- private final Int2ObjectMap> types = new Int2ObjectOpenHashMap<>(); ++ private final Int2ObjectMap> types = it.unimi.dsi.fastutil.ints.Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>()); // Paper + private final int hashCode; + + public RecursiveTypeFamily(final String name, final TypeTemplate template) { +diff --git a/src/main/java/com/mojang/datafixers/types/templates/Tag.java b/src/main/java/com/mojang/datafixers/types/templates/Tag.java +index ece3fb5f88..396637bbd1 100644 +--- a/src/main/java/com/mojang/datafixers/types/templates/Tag.java ++++ b/src/main/java/com/mojang/datafixers/types/templates/Tag.java +@@ -0,0 +0,0 @@ public final class Tag implements TypeTemplate { + public Pair> read(final DynamicOps ops, final T input) { + final Optional> map = ops.getMapValues(input); + final T nameObject = ops.createString(name); +- if (map.isPresent() && map.get().containsKey(nameObject)) { +- final T elementValue = map.get().get(nameObject); ++ // Paper start - performance - don't use containsKey ++ final T elementValue; ++ if (map.isPresent() && (elementValue = map.get().get(nameObject)) != null) { ++ // Paper end + final Optional value = element.read(ops, elementValue).getSecond(); + if (value.isPresent()) { + return Pair.of(ops.createMap(map.get().entrySet().stream().filter(e -> !Objects.equals(e.getKey(), nameObject)).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))), value); +diff --git a/src/main/java/com/mojang/datafixers/types/templates/TaggedChoice.java b/src/main/java/com/mojang/datafixers/types/templates/TaggedChoice.java +index e6dd31233a..77d64362b1 100644 +--- a/src/main/java/com/mojang/datafixers/types/templates/TaggedChoice.java ++++ b/src/main/java/com/mojang/datafixers/types/templates/TaggedChoice.java +@@ -0,0 +0,0 @@ public final class TaggedChoice implements TypeTemplate { + if (values.isPresent()) { + final Map map = values.get(); + final T nameObject = ops.createString(name); +- if (map.containsKey(nameObject)) { +- final Optional key = keyType.read(ops, map.get(nameObject)).getSecond(); +- if (!key.isPresent() || !types.containsKey(key.get())) { ++ // Paper start - performance - don't use containsKey ++ T mapValue = map.get(nameObject); ++ if (mapValue != null) { ++ final Optional key = keyType.read(ops, mapValue).getSecond(); ++ // also skip containsKey here ++ //noinspection OptionalIsPresent ++ K keyValue = key.isPresent() ? key.get() : null; ++ Type type = keyValue != null ? types.get(keyValue) : null; ++ if (type == null) { + if (DataFixerUpper.ERRORS_ARE_FATAL) { +- throw new IllegalArgumentException("Unsupported key: " + key.get() + " in " + this); ++ throw new IllegalArgumentException("Unsupported key: " + keyValue + " in " + this); + } else { +- LOGGER.warn("Unsupported key: {} in {}", key.get(), this); ++ LOGGER.warn("Unsupported key: {} in {}", keyValue, this); + return Pair.of(input, Optional.empty()); + } + } +- return types.get(key.get()).read(ops, input).mapSecond(vo -> vo.map(v -> Pair.of(key.get(), v))); ++ ++ return type.read(ops, input).mapSecond(vo -> vo.map(v -> Pair.of(keyValue, v))); ++ // Paper end + } + } + return Pair.of(input, Optional.empty()); +@@ -0,0 +0,0 @@ public final class TaggedChoice implements TypeTemplate { + + @Override + public T write(final DynamicOps ops, final T rest, final Pair value) { +- if (!types.containsKey(value.getFirst())) { ++ // Paper start - performance - don't use containsKey ++ final Type type = types.get(value.getFirst()); ++ if (type == null) { + // TODO: better error handling? + // TODO: See todo in read method + throw new IllegalArgumentException("Unsupported key: " + value.getFirst() + " in " + this); + } +- final Type type = types.get(value.getFirst()); ++ // Paper end + return capWrite(ops, type, value.getFirst(), value.getSecond(), rest); + } + +-- \ No newline at end of file diff --git a/Spigot-Server-Patches/Fix-concurrency-issues-in-DataFixers.patch b/Spigot-Server-Patches/Fix-concurrency-issues-in-DataFixers.patch deleted file mode 100644 index 054de2a80a..0000000000 --- a/Spigot-Server-Patches/Fix-concurrency-issues-in-DataFixers.patch +++ /dev/null @@ -1,144 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 3 Sep 2018 22:18:38 -0400 -Subject: [PATCH] Fix concurrency issues in DataFixers - -We are seeing issues with DataFixers being not thread safe in async chunks -and even in some spigot packet sending code. - -There are a few more global objects that are mutated that need to -be synchronized to be safe for use over multiple threads. - -There may be more cases, but these are extremely obvious ones. - -diff --git a/src/main/java/com/mojang/datafixers/DataFixerUpper.java b/src/main/java/com/mojang/datafixers/DataFixerUpper.java -index fb2c380f8a..c8e7a8aa10 100644 ---- a/src/main/java/com/mojang/datafixers/DataFixerUpper.java -+++ b/src/main/java/com/mojang/datafixers/DataFixerUpper.java -@@ -0,0 +0,0 @@ public class DataFixerUpper implements DataFixer { - private final Int2ObjectSortedMap schemas; - private final List globalList; - private final IntSortedSet fixerVersions; -- private final Long2ObjectMap rules = new Long2ObjectOpenHashMap<>(); -+ private final Long2ObjectMap rules = it.unimi.dsi.fastutil.longs.Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); // Paper - - protected DataFixerUpper(final Int2ObjectSortedMap schemas, final List globalList, final IntSortedSet fixerVersions) { - this.schemas = schemas; -@@ -0,0 +0,0 @@ public class DataFixerUpper implements DataFixer { - final int expandedDataVersion = DataFixUtils.makeKey(dataVersion); - - final long key = (long) expandedVersion << 32 | expandedDataVersion; -- if (!rules.containsKey(key)) { -+ rules.computeIfAbsent(key, k -> { // Paper - final List rules = Lists.newArrayList(); - for (final DataFix fix : globalList) { - final int fixVersion = fix.getVersionKey(); -@@ -0,0 +0,0 @@ public class DataFixerUpper implements DataFixer { - rules.add(fixRule); - } - } -- this.rules.put(key, TypeRewriteRule.seq(rules)); -- } -+ return TypeRewriteRule.seq(rules); // Paper -+ }); // Paper - return rules.get(key); - } - -diff --git a/src/main/java/com/mojang/datafixers/functions/PointFree.java b/src/main/java/com/mojang/datafixers/functions/PointFree.java -index 0d88490f77..028942b8ea 100644 ---- a/src/main/java/com/mojang/datafixers/functions/PointFree.java -+++ b/src/main/java/com/mojang/datafixers/functions/PointFree.java -@@ -0,0 +0,0 @@ public abstract class PointFree { - private Function, T> value; - - @SuppressWarnings("ConstantConditions") -- public Function, T> evalCached() { -+ public synchronized Function, T> evalCached() { // Paper - if (!initialized) { - initialized = true; - value = eval(); -diff --git a/src/main/java/com/mojang/datafixers/schemas/Schema.java b/src/main/java/com/mojang/datafixers/schemas/Schema.java -index 7c67d989e0..45f3ef5957 100644 ---- a/src/main/java/com/mojang/datafixers/schemas/Schema.java -+++ b/src/main/java/com/mojang/datafixers/schemas/Schema.java -@@ -0,0 +0,0 @@ import java.util.function.Function; - import java.util.function.Supplier; - - public class Schema { -- protected final Object2IntMap RECURSIVE_TYPES = new Object2IntOpenHashMap<>(); -- private final Map> TYPE_TEMPLATES = Maps.newHashMap(); -+ protected final Object2IntMap RECURSIVE_TYPES = it.unimi.dsi.fastutil.objects.Object2IntMaps.synchronize(new Object2IntOpenHashMap<>()); // Paper -+ private final Map> TYPE_TEMPLATES = Maps.newConcurrentMap(); // Paper - private final Map> TYPES; - private final int versionKey; - private final String name; -@@ -0,0 +0,0 @@ public class Schema { - } - - protected Map> buildTypes() { -- final Map> types = Maps.newHashMap(); -+ final Map> types = Maps.newConcurrentMap(); // Paper - - final List templates = Lists.newArrayList(); - -+ synchronized (RECURSIVE_TYPES) { // Paper - for (final Object2IntMap.Entry entry : RECURSIVE_TYPES.object2IntEntrySet()) { - templates.add(DSL.check(entry.getKey(), entry.getIntValue(), getTemplate(entry.getKey()))); -- } -+ } } // Paper - - final TypeTemplate choice = templates.stream().reduce(DSL::or).get(); - final TypeFamily family = new RecursiveTypeFamily(name, choice); - -+ synchronized (TYPE_TEMPLATES) { // Paper - for (final String name : TYPE_TEMPLATES.keySet()) { - final Type type; - if (RECURSIVE_TYPES.containsKey(name)) { -@@ -0,0 +0,0 @@ public class Schema { - type = getTemplate(name).apply(family).apply(-1); - } - types.put(name, type); -- } -+ } } // Paper - return types; - } - -@@ -0,0 +0,0 @@ public class Schema { - } - - public TypeTemplate id(final String name) { -+ synchronized (RECURSIVE_TYPES) { // Paper - if (RECURSIVE_TYPES.containsKey(name)) { -- return DSL.id(RECURSIVE_TYPES.get(name)); -- } -+ return DSL.id(RECURSIVE_TYPES.getInt(name)); // Paper -+ } } // Paper - return getTemplate(name); - } - -@@ -0,0 +0,0 @@ public class Schema { - public void registerType(final boolean recursive, final DSL.TypeReference type, final Supplier template) { - TYPE_TEMPLATES.put(type.typeName(), template); - // TODO: calculate recursiveness instead of hardcoding -+ synchronized (RECURSIVE_TYPES) { // Paper - if (recursive && !RECURSIVE_TYPES.containsKey(type.typeName())) { - RECURSIVE_TYPES.put(type.typeName(), RECURSIVE_TYPES.size()); -- } -+ } } // Paper - } - - public int getVersionKey() { -diff --git a/src/main/java/com/mojang/datafixers/types/families/RecursiveTypeFamily.java b/src/main/java/com/mojang/datafixers/types/families/RecursiveTypeFamily.java -index 4a1f906837..93c2f565fd 100644 ---- a/src/main/java/com/mojang/datafixers/types/families/RecursiveTypeFamily.java -+++ b/src/main/java/com/mojang/datafixers/types/families/RecursiveTypeFamily.java -@@ -0,0 +0,0 @@ public final class RecursiveTypeFamily implements TypeFamily { - private final TypeTemplate template; - private final int size; - -- private final Int2ObjectMap> types = new Int2ObjectOpenHashMap<>(); -+ private final Int2ObjectMap> types = it.unimi.dsi.fastutil.ints.Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>()); // Paper - private final int hashCode; - - public RecursiveTypeFamily(final String name, final TypeTemplate template) { --- \ No newline at end of file diff --git a/scripts/importmcdev.sh b/scripts/importmcdev.sh index 156bf3893b..dbfda717fe 100755 --- a/scripts/importmcdev.sh +++ b/scripts/importmcdev.sh @@ -105,7 +105,12 @@ done importLibrary com.mojang datafixerupper com/mojang/datafixers \ schemas/Schema.java \ DataFixerUpper.java \ + NamedChoiceFinder.java \ functions/PointFree.java \ + types/Type.java \ + types/DynamicOps.java \ + types/templates/Tag.java \ + types/templates/TaggedChoice.java \ types/families/RecursiveTypeFamily.java # dont forget \ at end of each line but last