/* * Copyright (c) Forge Development LLC and contributors * SPDX-License-Identifier: LGPL-2.1-only */ package net.minecraftforge.registries; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; 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 net.minecraft.Util; import net.minecraft.core.Holder; import net.minecraft.core.HolderGetter; import net.minecraft.core.HolderSet; import net.minecraft.core.MappedRegistry; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.util.RandomSource; import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; 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.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; class NamespacedWrapper extends MappedRegistry implements ILockableRegistry { static final Logger LOGGER = LogUtils.getLogger(); private final ForgeRegistry delegate; @Nullable private final Function> intrusiveHolderCallback; private final Multimap, Supplier> optionalTags = Multimaps.newSetMultimap(new IdentityHashMap<>(), HashSet::new); boolean locked = false; Lifecycle registryLifecycle = Lifecycle.stable(); private boolean frozen = false; // Frozen is vanilla's variant of locked, but it can be unfrozen private List> holdersSorted; private ObjectList> holdersById = new ObjectArrayList<>(256); private Map> holdersByName = new HashMap<>(); private Map> holders = new IdentityHashMap<>(); private RegistryManager stage; private volatile Map, HolderSet.Named> tags = new IdentityHashMap<>(); NamespacedWrapper(ForgeRegistry fowner, Function> intrusiveHolderCallback, RegistryManager stage) { super(fowner.getRegistryKey(), Lifecycle.stable(), intrusiveHolderCallback != null); this.delegate = fowner; this.intrusiveHolderCallback = intrusiveHolderCallback; this.stage = stage; } @Override public Holder.Reference registerMapping(int id, ResourceKey key, T value, Lifecycle lifecycle) { if (locked) throw new IllegalStateException("Can not register to a locked registry. Modder should use Forge Register methods."); Validate.notNull(value); markKnown(); this.registryLifecycle = this.registryLifecycle.add(lifecycle); int realId = this.delegate.add(id, key.location(), value); if (realId != id && id != -1) LOGGER.warn("Registered object did not get ID it asked for. Name: {} Expected: {} Got: {}", key, id, realId); return getHolder(key, value); } @Override public Holder.Reference register(ResourceKey key, T value, Lifecycle lifecycle) { return registerMapping(-1, key, value, lifecycle); } // Reading Functions @Override @Nullable public T get(@Nullable ResourceLocation name) { return this.delegate.getRaw(name); //get without default } @Override public Optional getOptional(@Nullable ResourceLocation name) { return Optional.ofNullable(this.delegate.getRaw(name)); //get without default } @Override @Nullable public T get(@Nullable ResourceKey name) { return name == null ? null : this.delegate.getRaw(name.location()); //get without default } @Override @Nullable public ResourceLocation getKey(T value) { return this.delegate.getKey(value); } @Override public Optional> getResourceKey(T p_122755_) { return this.delegate.getResourceKey(p_122755_); } @Override public boolean containsKey(ResourceLocation key) { return this.delegate.containsKey(key); } @Override public boolean containsKey(ResourceKey key) { return this.delegate.getRegistryName().equals(key.registry()) && containsKey(key.location()); } @Override public int getId(@Nullable T value) { return this.delegate.getID(value); } @Override @Nullable public T byId(int id) { return this.delegate.getValue(id); } @Override public Lifecycle lifecycle(T value) { return Lifecycle.stable(); } @Override public Lifecycle registryLifecycle() { return this.registryLifecycle; } @Override public Iterator iterator() { return this.delegate.iterator(); } @Override public Set keySet() { return this.delegate.getKeys(); } @Override public Set> registryKeySet() { return this.delegate.getResourceKeys(); } @Override public Set, T>> entrySet() { return this.delegate.getEntries(); } @Override public boolean isEmpty() { return this.delegate.isEmpty(); } @Override public int size() { return this.delegate.size(); } /** * @deprecated Forge: For internal use only. Use the Register events when registering values. */ @Deprecated @Override public void lock() { this.locked = true; } @Override public Optional> getHolder(int id) { return id >= 0 && id < this.holdersById.size() ? Optional.ofNullable(this.holdersById.get(id)) : Optional.empty(); } @Override public Optional> getHolder(ResourceKey key) { return Optional.ofNullable(this.holdersByName.get(key.location())); } @Override public @NotNull Holder wrapAsHolder(@NotNull T value) { final Holder holder = this.holders.get(value); return holder == null ? Holder.direct(value) : holder; } Optional> getHolder(ResourceLocation location) { return Optional.ofNullable(this.holdersByName.get(location)); } Optional> getHolder(T value) { return Optional.ofNullable(this.holders.get(value)); } @Override public HolderGetter createRegistrationLookup() { this.validateWrite(); return new HolderGetter() { public Optional> get(ResourceKey p_259097_) { return Optional.of(this.getOrThrow(p_259097_)); } public Holder.Reference getOrThrow(ResourceKey p_259750_) { return NamespacedWrapper.this.getOrCreateHolderOrThrow(p_259750_); } public Optional> get(TagKey p_259486_) { return Optional.of(this.getOrThrow(p_259486_)); } public HolderSet.Named getOrThrow(TagKey p_260298_) { return NamespacedWrapper.this.getOrCreateTag(p_260298_); } }; } void validateWrite() { if (this.frozen) throw new IllegalStateException("Registry is already frozen"); } void validateWrite(ResourceKey key) { if (this.frozen) throw new IllegalStateException("Registry is already frozen (trying to add key " + key + ")"); } Holder.Reference getOrCreateHolderOrThrow(ResourceKey key) { return this.holdersByName.computeIfAbsent(key.location(), k -> { if (this.intrusiveHolderCallback != null) { throw new IllegalStateException("This registry can't create new holders without value"); } else { this.validateWrite(key); return Holder.Reference.createStandAlone(this.holderOwner(), key); } }); } @Override public Optional> getRandom(RandomSource rand) { return Util.getRandomSafe(this.getSortedHolders(), rand); } @Override public Stream> holders() { return this.getSortedHolders().stream(); } @Override public Stream, HolderSet.Named>> getTags() { return this.tags.entrySet().stream().map(e -> Pair.of(e.getKey(), e.getValue())); } @Override public HolderSet.Named getOrCreateTag(TagKey name) { HolderSet.Named named = this.tags.get(name); if (named == null) { named = createTag(name); // They use volatile and set it this way to not have the performance penalties of synced read access. But this makes a lot of new maps.. We need to look into performance alternatives. Map, HolderSet.Named> map = new IdentityHashMap<>(this.tags); map.put(name, named); this.tags = map; } return named; } void addOptionalTag(TagKey name, @NotNull Set> defaults) { this.optionalTags.putAll(name, defaults); } @Override public Stream> getTagNames() { return this.tags.keySet().stream(); } //TODO: Move this to ValidateCallback? @Override public Registry freeze() { this.frozen = true; List unregistered = this.holdersByName.entrySet().stream() .filter(e -> !e.getValue().isBound()) .map(Map.Entry::getKey).sorted().toList(); if (!unregistered.isEmpty()) throw new IllegalStateException("Unbound values in registry " + this.key() + ": " + unregistered.stream().map(ResourceLocation::toString).collect(Collectors.joining(", \n\t"))); if (this.unregisteredIntrusiveHolders != null && this.unregisteredIntrusiveHolders.values().stream().anyMatch(r -> !r.isBound() && r.getType() == Holder.Reference.Type.INTRUSIVE)) { throw new IllegalStateException("Some intrusive holders were not registered: " + this.unregisteredIntrusiveHolders.values() + " Hint: Did you register all your registry objects? Registry stage: " + stage.getName()); } return this; } @Override public Holder.Reference createIntrusiveHolder(T value) { if (this.intrusiveHolderCallback == null) throw new IllegalStateException("This registry can't create intrusive holders"); this.validateWrite(); return super.createIntrusiveHolder(value); } @Override public Optional> getTag(TagKey name) { return Optional.ofNullable(this.tags.get(name)); } @Override public void bindTags(Map, List>> newTags) { Map, List>> holderToTag = new IdentityHashMap<>(); this.holdersByName.values().forEach(v -> holderToTag.put(v, new ArrayList<>())); newTags.forEach((name, values) -> values.forEach(holder -> addTagToHolder(holderToTag, name, holder))); Set> set = new HashSet<>(Sets.difference(this.tags.keySet(), newTags.keySet())); set.removeAll(this.optionalTags.keySet()); if (!set.isEmpty()) LOGGER.warn("Not all defined tags for registry {} are present in data pack: {}", this.key(), set.stream().map(k -> k.location().toString()).sorted() .collect(Collectors.joining(", \n\t"))); Map, HolderSet.Named> tmpTags = new IdentityHashMap<>(this.tags); newTags.forEach((k, v) -> tmpTags.computeIfAbsent(k, this::createTag).bind(v)); Set> defaultedTags = Sets.difference(this.optionalTags.keySet(), newTags.keySet()); defaultedTags.forEach(name -> { List> defaults = this.optionalTags.get(name).stream() .map(valueSupplier -> getHolder(valueSupplier.get()).orElse(null)) .filter(Objects::nonNull) .distinct() .toList(); defaults.forEach(holder -> addTagToHolder(holderToTag, name, holder)); tmpTags.computeIfAbsent(name, this::createTag).bind(defaults); }); holderToTag.forEach(Holder.Reference::bindTags); this.tags = tmpTags; this.delegate.onBindTags(this.tags, defaultedTags); } private void addTagToHolder(Map, List>> holderToTag, TagKey name, Holder holder) { if (!holder.canSerializeIn(this.holderOwner())) throw new IllegalStateException("Can't create named set " + name + " containing value " + holder + " from outside registry " + this); if (!(holder instanceof Holder.Reference)) throw new IllegalStateException("Found direct holder " + holder + " value in tag " + name); holderToTag.get((Holder.Reference) holder).add(name); } @Override public void resetTags() { this.tags.values().forEach(t -> t.bind(List.of())); this.holders.values().forEach(v -> v.bindTags(Set.of())); } @Override public void unfreeze() { this.frozen = false; } boolean isFrozen() { return this.frozen; } boolean isIntrusive() { return this.intrusiveHolderCallback != null; } @Nullable Holder.Reference onAdded(RegistryManager stage, int id, ResourceKey key, T newValue, T oldValue) { if (stage != RegistryManager.ACTIVE && (this.intrusiveHolderCallback == null || !stage.isStaging())) // Intrusive handlers need updating in staging. return null; // Only do holder shit on the active registries, not pending or snapshots. //Holder.Reference oldHolder = oldValue == null ? null : getHolder(key, oldValue); // Do we need to do anything with the old holder? We cant update its pointer unless its non-intrusive... // And if thats the case, then we *should* get its reference in newHolder Holder.Reference newHolder = getHolder(key, newValue); this.holdersById.size(Math.max(this.holdersById.size(), id + 1)); this.holdersById.set(id, newHolder); this.holdersByName.put(key.location(), newHolder); this.holders.put(newValue, newHolder); if (this.unregisteredIntrusiveHolders != null) { this.unregisteredIntrusiveHolders.remove(newValue); newHolder.bindKey(key); } newHolder.bindValue(newValue); this.holdersSorted = null; return newHolder; } @SuppressWarnings("deprecation") private HolderSet.Named createTag(TagKey name) { return HolderSet.emptyNamed(this.holderOwner(), name); } private Holder.Reference getHolder(ResourceKey key, T value) { if (this.intrusiveHolderCallback != null) return this.intrusiveHolderCallback.apply(value); return this.holdersByName.computeIfAbsent(key.location(), k -> Holder.Reference.createStandAlone(this.holderOwner(), key)); } private List> getSortedHolders() { if (this.holdersSorted == null) this.holdersSorted = this.holdersById.stream().filter(Objects::nonNull).toList(); return this.holdersSorted; } public static class Factory implements IForgeRegistry.CreateCallback, IForgeRegistry.AddCallback { public static final ResourceLocation ID = new ResourceLocation("forge", "registry_defaulted_wrapper"); @Override public void onCreate(IForgeRegistryInternal owner, RegistryManager stage) { ForgeRegistry fowner = (ForgeRegistry) owner; owner.setSlaveMap(ID, new NamespacedWrapper(fowner, fowner.getBuilder().getIntrusiveHolderCallback(), stage)); } @Override @SuppressWarnings("unchecked") public void onAdd(IForgeRegistryInternal owner, RegistryManager stage, int id, ResourceKey key, V value, V oldValue) { owner.getSlaveMap(ID, NamespacedWrapper.class).onAdded(stage, id, key, value, oldValue); } } }