package net.minecraft.core; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.mojang.datafixers.util.Pair; import com.mojang.logging.LogUtils; 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.stream.Collectors; import java.util.stream.Stream; import net.minecraft.Util; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.util.RandomSource; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class MappedRegistry implements WritableRegistry { private static final Logger LOGGER = LogUtils.getLogger(); 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 volatile Map, HolderSet.Named> tags = new IdentityHashMap(); private boolean frozen; @Nullable private Map> unregisteredIntrusiveHolders; private final HolderLookup.RegistryLookup lookup = new HolderLookup.RegistryLookup() { @Override public ResourceKey> key() { return MappedRegistry.this.key; } @Override public Lifecycle registryLifecycle() { return MappedRegistry.this.registryLifecycle(); } @Override public Optional> get(ResourceKey resourceKey) { return MappedRegistry.this.getHolder(resourceKey); } @Override public Stream> listElements() { return MappedRegistry.this.holders(); } @Override public Optional> get(TagKey tagKey) { return MappedRegistry.this.getTag(tagKey); } @Override public Stream> listTags() { return MappedRegistry.this.getTags().map(Pair::getSecond); } }; private final Object tagAdditionLock = new Object(); 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())) { Util.pauseInIde((T)(new IllegalStateException("Adding duplicate key '" + key + "' to registry"))); } if (this.byValue.containsKey(value)) { Util.pauseInIde((T)(new IllegalStateException("Adding duplicate value '" + value + "' to registry"))); } 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.holderOwner(), 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 get(@Nullable ResourceKey key) { return getValueFromNullable((Holder.Reference)this.byKey.get(key)); } @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> getHolder(int id) { return id >= 0 && id < this.byId.size() ? Optional.ofNullable((Holder.Reference)this.byId.get(id)) : Optional.empty(); } @Override public Optional> getHolder(ResourceLocation location) { return Optional.ofNullable((Holder.Reference)this.byLocation.get(location)); } @Override public Optional> getHolder(ResourceKey key) { return Optional.ofNullable((Holder.Reference)this.byKey.get(key)); } @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.holderOwner(), 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 get(@Nullable ResourceLocation name) { Holder.Reference reference = (Holder.Reference)this.byLocation.get(name); 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> holders() { return this.byId.stream(); } @Override public Stream, HolderSet.Named>> getTags() { return this.tags.entrySet().stream().map(entry -> Pair.of((TagKey)entry.getKey(), (HolderSet.Named)entry.getValue())); } @Override public HolderSet.Named getOrCreateTag(TagKey key) { HolderSet.Named named = (HolderSet.Named)this.tags.get(key); if (named != null) { return named; } else { synchronized (this.tagAdditionLock) { named = (HolderSet.Named)this.tags.get(key); if (named != null) { return named; } else { named = this.createTag(key); Map, HolderSet.Named> map = new IdentityHashMap(this.tags); map.put(key, named); this.tags = map; return named; } } } } private HolderSet.Named createTag(TagKey key) { return new HolderSet.Named<>(this.holderOwner(), key); } @Override public Stream> getTagNames() { return this.tags.keySet().stream(); } @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; } 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.asLookup(), (T)object)); } } @Override public Optional> getTag(TagKey key) { return Optional.ofNullable((HolderSet.Named)this.tags.get(key)); } @Override public void bindTags(Map, List>> tagMap) { Map, List>> map = new IdentityHashMap(); this.byKey.values().forEach(reference -> map.put(reference, new ArrayList())); tagMap.forEach((tagKey, list) -> { for (Holder holder : list) { if (!holder.canSerializeIn(this.asLookup())) { throw new IllegalStateException("Can't create named set " + tagKey + " containing value " + holder + " from outside registry " + this); } if (!(holder instanceof Holder.Reference reference)) { throw new IllegalStateException("Found direct holder " + holder + " value in tag " + tagKey); } ((List)map.get(reference)).add(tagKey); } }); Set> set = Sets.>difference(this.tags.keySet(), tagMap.keySet()); if (!set.isEmpty()) { LOGGER.warn( "Not all defined tags for registry {} are present in data pack: {}", this.key(), set.stream().map(tagKey -> tagKey.location().toString()).sorted().collect(Collectors.joining(", ")) ); } synchronized (this.tagAdditionLock) { Map, HolderSet.Named> map2 = new IdentityHashMap(this.tags); tagMap.forEach((tagKey, list) -> ((HolderSet.Named)map2.computeIfAbsent(tagKey, this::createTag)).bind(list)); map.forEach(Holder.Reference::bindTags); this.tags = map2; } } @Override public void resetTags() { this.tags.values().forEach(named -> named.bind(List.of())); this.byKey.values().forEach(reference -> reference.bindTags(Set.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.getOrCreateTag(tagKey); } }; } @Override public HolderOwner holderOwner() { return this.lookup; } @Override public HolderLookup.RegistryLookup asLookup() { return this.lookup; } }