package net.minecraft.core; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterators; 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.Holder.Reference; import net.minecraft.core.HolderLookup.RegistryLookup.Delegate; import net.minecraft.core.HolderSet.Named; import net.minecraft.core.MappedRegistry.TagSet.1; import net.minecraft.core.MappedRegistry.TagSet.2; import net.minecraft.core.Registry.PendingTags; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.tags.TagLoader.LoadResult; 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, Reference> byKey = new HashMap(); private final Map> byValue = new IdentityHashMap(); private final Map, RegistrationInfo> registrationInfos = new IdentityHashMap(); private Lifecycle registryLifecycle; private final Map, 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 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 { Reference reference; if (this.unregisteredIntrusiveHolders != null) { reference = (Reference)this.unregisteredIntrusiveHolders.remove(value); if (reference == null) { throw new AssertionError("Missing intrusive holder for " + key + ":" + value); } reference.bindKey(key); } else { reference = (Reference)this.byKey.computeIfAbsent(key, resourceKey -> 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) { Reference reference = (Reference)this.byValue.get(value); return reference != null ? reference.key().location() : null; } @Override public Optional> getResourceKey(T value) { return Optional.ofNullable((Reference)this.byValue.get(value)).map(Reference::key); } @Override public int getId(@Nullable T value) { return this.toId.getInt(value); } @Nullable @Override public T getValue(@Nullable ResourceKey key) { return getValueFromNullable((Reference)this.byKey.get(key)); } @Nullable @Override public T byId(int id) { return (T)(id >= 0 && id < this.byId.size() ? ((Reference)this.byId.get(id)).value() : null); } @Override public Optional> get(int index) { return index >= 0 && index < this.byId.size() ? Optional.ofNullable((Reference)this.byId.get(index)) : Optional.empty(); } @Override public Optional> get(ResourceLocation key) { return Optional.ofNullable((Reference)this.byLocation.get(key)); } @Override public Optional> get(ResourceKey resourceKey) { return Optional.ofNullable((Reference)this.byKey.get(resourceKey)); } @Override public Optional> getAny() { return this.byId.isEmpty() ? Optional.empty() : Optional.of((Reference)this.byId.getFirst()); } @Override public Holder wrapAsHolder(T value) { Reference reference = (Reference)this.byValue.get(value); return (Holder)(reference != null ? reference : Holder.direct(value)); } Reference getOrCreateHolderOrThrow(ResourceKey key) { return (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 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 key) { Reference reference = (Reference)this.byLocation.get(key); return getValueFromNullable(reference); } @Nullable private static T getValueFromNullable(@Nullable 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(Util.mapValuesLazy(this.byKey, Holder::value).entrySet()); } @Override public Stream> listElements() { return this.byId.stream(); } @Override public Stream> getTags() { return this.allTags.getTags(); } Named getOrCreateTagForRegistration(TagKey key) { return (Named)this.frozenTags.computeIfAbsent(key, this::createTag); } private Named createTag(TagKey key) { return new 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 -> !((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 -> !((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 Reference createIntrusiveHolder(T value) { if (this.unregisteredIntrusiveHolders == null) { throw new IllegalStateException("This registry can't create intrusive holders"); } else { this.validateWrite(); return (Reference)this.unregisteredIntrusiveHolders.computeIfAbsent(value, object -> Reference.createIntrusive(this, (T)object)); } } @Override public Optional> get(TagKey tagKey) { return this.allTags.get(tagKey); } private Reference validateAndUnwrapTagElement(TagKey key, Holder value) { if (!value.canSerializeIn(this)) { throw new IllegalStateException("Can't create named set " + key + " containing value " + value + " from outside registry " + this); } else if (value instanceof Reference reference) { return reference; } else { throw new IllegalStateException("Found direct holder " + value + " value in tag " + key); } } @Override public void bindTag(TagKey tag, List> values) { this.validateWrite(); this.getOrCreateTagForRegistration(tag).bind(values); } 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) { Reference reference = this.validateAndUnwrapTagElement(tagKey, holder); ((List)map.get(reference)).add(tagKey); } }); map.forEach(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 Reference getOrThrow(ResourceKey resourceKey) { return MappedRegistry.this.getOrCreateHolderOrThrow(resourceKey); } @Override public Optional> get(TagKey tagKey) { return Optional.of(this.getOrThrow(tagKey)); } @Override public Named getOrThrow(TagKey tagKey) { return MappedRegistry.this.getOrCreateTagForRegistration(tagKey); } }; } @Override public PendingTags prepareTagReload(LoadResult loadResult) { if (!this.frozen) { throw new IllegalStateException("Invalid method used for tag loading"); } else { Builder, Named> builder = ImmutableMap.builder(); final Map, List>> map = new HashMap(); loadResult.tags().forEach((tagKey, list) -> { Named named = (Named)this.frozenTags.get(tagKey); if (named == null) { named = this.createTag(tagKey); } builder.put(tagKey, named); map.put(tagKey, List.copyOf(list)); }); final ImmutableMap, 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 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, Named> map) { return new 2(map); } boolean isBound(); Optional> get(TagKey key); void forEach(BiConsumer, ? super Named> action); Stream> getTags(); } }