package net.minecraft.core; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; import com.google.common.collect.ImmutableMap.Builder; import com.mojang.serialization.Lifecycle; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectList; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.Map.Entry; import java.util.function.BiConsumer; import java.util.stream.Stream; import net.minecraft.Util; import net.minecraft.core.HolderLookup.RegistryLookup.Delegate; import net.minecraft.core.MappedRegistry.TagSet.1; import net.minecraft.core.MappedRegistry.TagSet.2; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.tags.TagLoader; import net.minecraft.util.RandomSource; import org.jetbrains.annotations.Nullable; public class MappedRegistry implements WritableRegistry { private final ResourceKey> key; private final ObjectList> byId = new ObjectArrayList<>(256); private final Reference2IntMap toId = Util.make( new Reference2IntOpenHashMap<>(), reference2IntOpenHashMap -> reference2IntOpenHashMap.defaultReturnValue(-1) ); private final Map> byLocation = new HashMap(); private final Map, Holder.Reference> byKey = new HashMap(); private final Map> byValue = new IdentityHashMap(); private final Map, RegistrationInfo> registrationInfos = new IdentityHashMap(); private Lifecycle registryLifecycle; private final Map, HolderSet.Named> frozenTags = new IdentityHashMap(); MappedRegistry.TagSet allTags = MappedRegistry.TagSet.unbound(); private boolean frozen; @Nullable private Map> unregisteredIntrusiveHolders; @Override public Stream> listTags() { return this.getTags(); } public MappedRegistry(ResourceKey> key, Lifecycle registryLifecycle) { this(key, registryLifecycle, false); } public MappedRegistry(ResourceKey> key, Lifecycle registryLifecycle, boolean hasIntrusiveHolders) { this.key = key; this.registryLifecycle = registryLifecycle; if (hasIntrusiveHolders) { this.unregisteredIntrusiveHolders = new IdentityHashMap(); } } @Override public ResourceKey> key() { return this.key; } public String toString() { return "Registry[" + this.key + " (" + this.registryLifecycle + ")]"; } private void validateWrite() { if (this.frozen) { throw new IllegalStateException("Registry is already frozen"); } } private void validateWrite(ResourceKey key) { if (this.frozen) { throw new IllegalStateException("Registry is already frozen (trying to add key " + key + ")"); } } @Override public Holder.Reference register(ResourceKey key, T value, RegistrationInfo registrationInfo) { this.validateWrite(key); Objects.requireNonNull(key); Objects.requireNonNull(value); if (this.byLocation.containsKey(key.location())) { throw (IllegalStateException)Util.pauseInIde((T)(new IllegalStateException("Adding duplicate key '" + key + "' to registry"))); } else if (this.byValue.containsKey(value)) { throw (IllegalStateException)Util.pauseInIde((T)(new IllegalStateException("Adding duplicate value '" + value + "' to registry"))); } else { Holder.Reference reference; if (this.unregisteredIntrusiveHolders != null) { reference = (Holder.Reference)this.unregisteredIntrusiveHolders.remove(value); if (reference == null) { throw new AssertionError("Missing intrusive holder for " + key + ":" + value); } reference.bindKey(key); } else { reference = (Holder.Reference)this.byKey.computeIfAbsent(key, resourceKey -> Holder.Reference.createStandAlone(this, resourceKey)); } this.byKey.put(key, reference); this.byLocation.put(key.location(), reference); this.byValue.put(value, reference); int i = this.byId.size(); this.byId.add(reference); this.toId.put(value, i); this.registrationInfos.put(key, registrationInfo); this.registryLifecycle = this.registryLifecycle.add(registrationInfo.lifecycle()); return reference; } } @Nullable @Override public ResourceLocation getKey(T value) { Holder.Reference reference = (Holder.Reference)this.byValue.get(value); return reference != null ? reference.key().location() : null; } @Override public Optional> getResourceKey(T value) { return Optional.ofNullable((Holder.Reference)this.byValue.get(value)).map(Holder.Reference::key); } @Override public int getId(@Nullable T value) { return this.toId.getInt(value); } @Nullable @Override public T getValue(@Nullable ResourceKey resourceKey) { return getValueFromNullable((Holder.Reference)this.byKey.get(resourceKey)); } @Nullable @Override public T byId(int id) { return (T)(id >= 0 && id < this.byId.size() ? ((Holder.Reference)this.byId.get(id)).value() : null); } @Override public Optional> get(int i) { return i >= 0 && i < this.byId.size() ? Optional.ofNullable((Holder.Reference)this.byId.get(i)) : Optional.empty(); } @Override public Optional> get(ResourceLocation resourceLocation) { return Optional.ofNullable((Holder.Reference)this.byLocation.get(resourceLocation)); } @Override public Optional> get(ResourceKey resourceKey) { return Optional.ofNullable((Holder.Reference)this.byKey.get(resourceKey)); } @Override public Optional> getAny() { return this.byId.isEmpty() ? Optional.empty() : Optional.of((Holder.Reference)this.byId.getFirst()); } @Override public Holder wrapAsHolder(T value) { Holder.Reference reference = (Holder.Reference)this.byValue.get(value); return (Holder)(reference != null ? reference : Holder.direct(value)); } Holder.Reference getOrCreateHolderOrThrow(ResourceKey key) { return (Holder.Reference)this.byKey.computeIfAbsent(key, resourceKey -> { if (this.unregisteredIntrusiveHolders != null) { throw new IllegalStateException("This registry can't create new holders without value"); } else { this.validateWrite(resourceKey); return Holder.Reference.createStandAlone(this, resourceKey); } }); } @Override public int size() { return this.byKey.size(); } @Override public Optional registrationInfo(ResourceKey key) { return Optional.ofNullable((RegistrationInfo)this.registrationInfos.get(key)); } @Override public Lifecycle registryLifecycle() { return this.registryLifecycle; } public Iterator iterator() { return Iterators.transform(this.byId.iterator(), Holder::value); } @Nullable @Override public T getValue(@Nullable ResourceLocation resourceLocation) { Holder.Reference reference = (Holder.Reference)this.byLocation.get(resourceLocation); return getValueFromNullable(reference); } @Nullable private static T getValueFromNullable(@Nullable Holder.Reference holder) { return holder != null ? holder.value() : null; } @Override public Set keySet() { return Collections.unmodifiableSet(this.byLocation.keySet()); } @Override public Set> registryKeySet() { return Collections.unmodifiableSet(this.byKey.keySet()); } @Override public Set, T>> entrySet() { return Collections.unmodifiableSet(Maps.transformValues(this.byKey, Holder::value).entrySet()); } @Override public Stream> listElements() { return this.byId.stream(); } @Override public Stream> getTags() { return this.allTags.getTags(); } HolderSet.Named getOrCreateTagForRegistration(TagKey tagKey) { return (HolderSet.Named)this.frozenTags.computeIfAbsent(tagKey, this::createTag); } private HolderSet.Named createTag(TagKey key) { return new HolderSet.Named<>(this, key); } @Override public boolean isEmpty() { return this.byKey.isEmpty(); } @Override public Optional> getRandom(RandomSource random) { return Util.getRandomSafe(this.byId, random); } @Override public boolean containsKey(ResourceLocation name) { return this.byLocation.containsKey(name); } @Override public boolean containsKey(ResourceKey key) { return this.byKey.containsKey(key); } @Override public Registry freeze() { if (this.frozen) { return this; } else { this.frozen = true; this.byValue.forEach((object, reference) -> reference.bindValue(object)); List list = this.byKey .entrySet() .stream() .filter(entry -> !((Holder.Reference)entry.getValue()).isBound()) .map(entry -> ((ResourceKey)entry.getKey()).location()) .sorted() .toList(); if (!list.isEmpty()) { throw new IllegalStateException("Unbound values in registry " + this.key() + ": " + list); } else { if (this.unregisteredIntrusiveHolders != null) { if (!this.unregisteredIntrusiveHolders.isEmpty()) { throw new IllegalStateException("Some intrusive holders were not registered: " + this.unregisteredIntrusiveHolders.values()); } this.unregisteredIntrusiveHolders = null; } if (this.allTags.isBound()) { throw new IllegalStateException("Tags already present before freezing"); } else { List list2 = this.frozenTags .entrySet() .stream() .filter(entry -> !((HolderSet.Named)entry.getValue()).isBound()) .map(entry -> ((TagKey)entry.getKey()).location()) .sorted() .toList(); if (!list2.isEmpty()) { throw new IllegalStateException("Unbound tags in registry " + this.key() + ": " + list2); } else { this.allTags = MappedRegistry.TagSet.fromMap(this.frozenTags); this.refreshTagsInHolders(); return this; } } } } } @Override public Holder.Reference createIntrusiveHolder(T value) { if (this.unregisteredIntrusiveHolders == null) { throw new IllegalStateException("This registry can't create intrusive holders"); } else { this.validateWrite(); return (Holder.Reference)this.unregisteredIntrusiveHolders.computeIfAbsent(value, object -> Holder.Reference.createIntrusive(this, (T)object)); } } @Override public Optional> get(TagKey tagKey) { return this.allTags.get(tagKey); } private Holder.Reference validateAndUnwrapTagElement(TagKey tagKey, Holder holder) { if (!holder.canSerializeIn(this)) { throw new IllegalStateException("Can't create named set " + tagKey + " containing value " + holder + " from outside registry " + this); } else if (holder instanceof Holder.Reference reference) { return reference; } else { throw new IllegalStateException("Found direct holder " + holder + " value in tag " + tagKey); } } @Override public void bindTag(TagKey tagKey, List> list) { this.validateWrite(); this.getOrCreateTagForRegistration(tagKey).bind(list); } void refreshTagsInHolders() { Map, List>> map = new IdentityHashMap(); this.byKey.values().forEach(reference -> map.put(reference, new ArrayList())); this.allTags.forEach((tagKey, named) -> { for (Holder holder : named) { Holder.Reference reference = this.validateAndUnwrapTagElement(tagKey, holder); ((List)map.get(reference)).add(tagKey); } }); map.forEach(Holder.Reference::bindTags); } public void bindAllTagsToEmpty() { this.validateWrite(); this.frozenTags.values().forEach(named -> named.bind(List.of())); } @Override public HolderGetter createRegistrationLookup() { this.validateWrite(); return new HolderGetter() { @Override public Optional> get(ResourceKey resourceKey) { return Optional.of(this.getOrThrow(resourceKey)); } @Override public Holder.Reference getOrThrow(ResourceKey resourceKey) { return MappedRegistry.this.getOrCreateHolderOrThrow(resourceKey); } @Override public Optional> get(TagKey tagKey) { return Optional.of(this.getOrThrow(tagKey)); } @Override public HolderSet.Named getOrThrow(TagKey tagKey) { return MappedRegistry.this.getOrCreateTagForRegistration(tagKey); } }; } @Override public Registry.PendingTags prepareTagReload(TagLoader.LoadResult loadResult) { if (!this.frozen) { throw new IllegalStateException("Invalid method used for tag loading"); } else { Builder, HolderSet.Named> builder = ImmutableMap.builder(); final Map, List>> map = new HashMap(); loadResult.tags().forEach((tagKey, list) -> { HolderSet.Named named = (HolderSet.Named)this.frozenTags.get(tagKey); if (named == null) { named = this.createTag(tagKey); } builder.put(tagKey, named); map.put(tagKey, List.copyOf(list)); }); final ImmutableMap, HolderSet.Named> immutableMap = builder.build(); final HolderLookup.RegistryLookup registryLookup = new Delegate() { public HolderLookup.RegistryLookup parent() { return MappedRegistry.this; } public Optional> get(TagKey tagKey) { return Optional.ofNullable(immutableMap.get(tagKey)); } public Stream> listTags() { return immutableMap.values().stream(); } }; return new Registry.PendingTags() { @Override public ResourceKey> key() { return MappedRegistry.this.key(); } @Override public int size() { return map.size(); } @Override public HolderLookup.RegistryLookup lookup() { return registryLookup; } @Override public void apply() { immutableMap.forEach((tagKey, named) -> { List> list = (List>)map.getOrDefault(tagKey, List.of()); named.bind(list); }); MappedRegistry.this.allTags = MappedRegistry.TagSet.fromMap(immutableMap); MappedRegistry.this.refreshTagsInHolders(); } }; } } interface TagSet { static MappedRegistry.TagSet unbound() { return new 1(); } static MappedRegistry.TagSet fromMap(Map, HolderSet.Named> map) { return new 2(map); } boolean isBound(); Optional> get(TagKey tagKey); void forEach(BiConsumer, ? super HolderSet.Named> biConsumer); Stream> getTags(); } }