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.core.component.DataComponentLookup; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; import net.minecraft.tags.TagKey; import net.minecraft.tags.TagLoader; import net.minecraft.util.RandomSource; import net.minecraft.util.Util; import org.jspecify.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<>(), t -> t.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(); private MappedRegistry.TagSet allTags = MappedRegistry.TagSet.unbound(); @Nullable private DataComponentLookup componentLookup; private boolean frozen; @Nullable private Map> unregisteredIntrusiveHolders; @Override public Stream> listTags() { return this.getTags(); } public MappedRegistry(final ResourceKey> key, final Lifecycle lifecycle) { this(key, lifecycle, false); } public MappedRegistry(final ResourceKey> key, final Lifecycle initialLifecycle, final boolean intrusiveHolders) { this.key = key; this.registryLifecycle = initialLifecycle; if (intrusiveHolders) { 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(final ResourceKey key) { if (this.frozen) { throw new IllegalStateException("Registry is already frozen (trying to add key " + key + ")"); } } @Override public Holder.Reference register(final ResourceKey key, final T value, final RegistrationInfo registrationInfo) { this.validateWrite(key); Objects.requireNonNull(key); Objects.requireNonNull(value); if (this.byLocation.containsKey(key.identifier())) { 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 holder; if (this.unregisteredIntrusiveHolders != null) { holder = (Holder.Reference)this.unregisteredIntrusiveHolders.remove(value); if (holder == null) { throw new AssertionError("Missing intrusive holder for " + key + ":" + value); } holder.bindKey(key); } else { holder = (Holder.Reference)this.byKey.computeIfAbsent(key, k -> Holder.Reference.createStandAlone(this, k)); } this.byKey.put(key, holder); this.byLocation.put(key.identifier(), holder); this.byValue.put(value, holder); int newId = this.byId.size(); this.byId.add(holder); this.toId.put(value, newId); this.registrationInfos.put(key, registrationInfo); this.registryLifecycle = this.registryLifecycle.add(registrationInfo.lifecycle()); return holder; } } @Nullable @Override public Identifier getKey(final T thing) { Holder.Reference holder = (Holder.Reference)this.byValue.get(thing); return holder != null ? holder.key().identifier() : null; } @Override public Optional> getResourceKey(final T thing) { return Optional.ofNullable((Holder.Reference)this.byValue.get(thing)).map(Holder.Reference::key); } @Override public int getId(@Nullable final T thing) { return this.toId.getInt(thing); } @Nullable @Override public T getValue(@Nullable final ResourceKey key) { return getValueFromNullable((Holder.Reference)this.byKey.get(key)); } @Nullable @Override public T byId(final int id) { return (T)(id >= 0 && id < this.byId.size() ? ((Holder.Reference)this.byId.get(id)).value() : null); } @Override public Optional> get(final int id) { return id >= 0 && id < this.byId.size() ? Optional.ofNullable((Holder.Reference)this.byId.get(id)) : Optional.empty(); } @Override public Optional> get(final Identifier id) { return Optional.ofNullable((Holder.Reference)this.byLocation.get(id)); } @Override public Optional> get(final ResourceKey id) { return Optional.ofNullable((Holder.Reference)this.byKey.get(id)); } @Override public Optional> getAny() { return this.byId.isEmpty() ? Optional.empty() : Optional.of((Holder.Reference)this.byId.getFirst()); } @Override public Holder wrapAsHolder(final T value) { Holder.Reference existingHolder = (Holder.Reference)this.byValue.get(value); return (Holder)(existingHolder != null ? existingHolder : Holder.direct(value)); } private Holder.Reference getOrCreateHolderOrThrow(final ResourceKey key) { return (Holder.Reference)this.byKey.computeIfAbsent(key, id -> { if (this.unregisteredIntrusiveHolders != null) { throw new IllegalStateException("This registry can't create new holders without value"); } else { this.validateWrite(id); return Holder.Reference.createStandAlone(this, id); } }); } @Override public int size() { return this.byKey.size(); } @Override public Optional registrationInfo(final ResourceKey element) { return Optional.ofNullable((RegistrationInfo)this.registrationInfos.get(element)); } @Override public Lifecycle registryLifecycle() { return this.registryLifecycle; } public Iterator iterator() { return Iterators.transform(this.byId.iterator(), Holder::value); } @Nullable @Override public T getValue(@Nullable final Identifier key) { Holder.Reference result = (Holder.Reference)this.byLocation.get(key); return getValueFromNullable(result); } @Nullable private static T getValueFromNullable(@Nullable final Holder.Reference result) { return result != null ? result.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(); } private HolderSet.Named getOrCreateTagForRegistration(final TagKey tag) { return (HolderSet.Named)this.frozenTags.computeIfAbsent(tag, this::createTag); } private HolderSet.Named createTag(final TagKey tag) { return new HolderSet.Named<>(this, tag); } @Override public boolean isEmpty() { return this.byKey.isEmpty(); } @Override public Optional> getRandom(final RandomSource random) { return Util.getRandomSafe(this.byId, random); } @Override public boolean containsKey(final Identifier key) { return this.byLocation.containsKey(key); } @Override public boolean containsKey(final ResourceKey key) { return this.byKey.containsKey(key); } @Override public DataComponentLookup componentLookup() { return (DataComponentLookup)Objects.requireNonNull(this.componentLookup, "Registry not frozen yet"); } @Override public Registry freeze() { if (this.frozen) { return this; } else { this.frozen = true; this.byValue.forEach((value, holder) -> holder.bindValue(value)); List unboundEntries = this.byKey .entrySet() .stream() .filter(e -> !((Holder.Reference)e.getValue()).isBound()) .map(e -> ((ResourceKey)e.getKey()).identifier()) .sorted() .toList(); if (!unboundEntries.isEmpty()) { throw new IllegalStateException("Unbound values in registry " + this.key() + ": " + unboundEntries); } 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 unboundTags = this.frozenTags .entrySet() .stream() .filter(e -> !((HolderSet.Named)e.getValue()).isBound()) .map(e -> ((TagKey)e.getKey()).location()) .sorted() .toList(); if (!unboundTags.isEmpty()) { throw new IllegalStateException("Unbound tags in registry " + this.key() + ": " + unboundTags); } else { this.componentLookup = new DataComponentLookup<>(this.byId); this.allTags = MappedRegistry.TagSet.fromMap(this.frozenTags); this.refreshTagsInHolders(); return this; } } } } } @Override public Holder.Reference createIntrusiveHolder(final 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, v -> Holder.Reference.createIntrusive(this, (T)v)); } } @Override public Optional> get(final TagKey id) { return this.allTags.get(id); } private Holder.Reference validateAndUnwrapTagElement(final TagKey id, final Holder value) { if (!value.canSerializeIn(this)) { throw new IllegalStateException("Can't create named set " + id + " containing value " + value + " from outside registry " + this); } else if (value instanceof Holder.Reference reference) { return reference; } else { throw new IllegalStateException("Found direct holder " + value + " value in tag " + id); } } @Override public void bindTags(final Map, List>> pendingTags) { this.validateWrite(); pendingTags.forEach((id, values) -> this.getOrCreateTagForRegistration(id).bind(values)); } private void refreshTagsInHolders() { Map, List>> tagsForElement = new IdentityHashMap(); this.byKey.values().forEach(h -> tagsForElement.put(h, new ArrayList())); this.allTags.forEach((id, values) -> { for (Holder value : values) { Holder.Reference reference = this.validateAndUnwrapTagElement(id, value); ((List)tagsForElement.get(reference)).add(id); } }); tagsForElement.forEach(Holder.Reference::bindTags); } public void bindAllTagsToEmpty() { this.validateWrite(); this.frozenTags.values().forEach(e -> e.bind(List.of())); } @Override public HolderGetter createRegistrationLookup() { this.validateWrite(); return new HolderGetter() { { Objects.requireNonNull(MappedRegistry.this); } @Override public Optional> get(final ResourceKey id) { return Optional.of(this.getOrThrow(id)); } @Override public Holder.Reference getOrThrow(final ResourceKey id) { return MappedRegistry.this.getOrCreateHolderOrThrow(id); } @Override public Optional> get(final TagKey id) { return Optional.of(this.getOrThrow(id)); } @Override public HolderSet.Named getOrThrow(final TagKey id) { return MappedRegistry.this.getOrCreateTagForRegistration(id); } }; } @Override public Registry.PendingTags prepareTagReload(final TagLoader.LoadResult tags) { if (!this.frozen) { throw new IllegalStateException("Invalid method used for tag loading"); } else { Builder, HolderSet.Named> pendingTagsBuilder = ImmutableMap.builder(); final Map, List>> pendingContents = new HashMap(); tags.tags().forEach((id, contents) -> { HolderSet.Named tagToAdd = (HolderSet.Named)this.frozenTags.get(id); if (tagToAdd == null) { tagToAdd = this.createTag(id); } pendingTagsBuilder.put(id, tagToAdd); pendingContents.put(id, List.copyOf(contents)); }); final ImmutableMap, HolderSet.Named> pendingTags = pendingTagsBuilder.build(); final HolderLookup.RegistryLookup patchedHolder = new HolderLookup.RegistryLookup.Delegate() { { Objects.requireNonNull(MappedRegistry.this); } @Override public HolderLookup.RegistryLookup parent() { return MappedRegistry.this; } @Override public Optional> get(final TagKey id) { return Optional.ofNullable(pendingTags.get(id)); } @Override public Stream> listTags() { return pendingTags.values().stream(); } }; return new Registry.PendingTags() { { Objects.requireNonNull(MappedRegistry.this); } @Override public ResourceKey> key() { return MappedRegistry.this.key(); } @Override public int size() { return pendingContents.size(); } @Override public HolderLookup.RegistryLookup lookup() { return patchedHolder; } @Override public void apply() { pendingTags.forEach((id, tag) -> { List> values = (List>)pendingContents.getOrDefault(id, List.of()); tag.bind(values); }); MappedRegistry.this.allTags = MappedRegistry.TagSet.fromMap(pendingTags); MappedRegistry.this.refreshTagsInHolders(); } }; } } private interface TagSet { static MappedRegistry.TagSet unbound() { return new MappedRegistry.TagSet() { @Override public boolean isBound() { return false; } @Override public Optional> get(final TagKey id) { throw new IllegalStateException("Tags not bound, trying to access " + id); } @Override public void forEach(final BiConsumer, ? super HolderSet.Named> action) { throw new IllegalStateException("Tags not bound"); } @Override public Stream> getTags() { throw new IllegalStateException("Tags not bound"); } }; } static MappedRegistry.TagSet fromMap(final Map, HolderSet.Named> tags) { return new MappedRegistry.TagSet() { @Override public boolean isBound() { return true; } @Override public Optional> get(final TagKey id) { return Optional.ofNullable((HolderSet.Named)tags.get(id)); } @Override public void forEach(final BiConsumer, ? super HolderSet.Named> action) { tags.forEach(action); } @Override public Stream> getTags() { return tags.values().stream(); } }; } boolean isBound(); Optional> get(TagKey id); void forEach(BiConsumer, ? super HolderSet.Named> action); Stream> getTags(); } }