package net.minecraft.core; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.mojang.serialization.DynamicOps; import com.mojang.serialization.Lifecycle; import java.util.ArrayList; import java.util.HashMap; 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.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import net.minecraft.data.worldgen.BootstrapContext; import net.minecraft.resources.Identifier; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; import net.minecraft.tags.TagKey; import org.apache.commons.lang3.mutable.MutableObject; import org.jspecify.annotations.Nullable; public class RegistrySetBuilder { private final List> entries = new ArrayList(); private static HolderGetter wrapContextLookup(final HolderLookup.RegistryLookup original) { return new RegistrySetBuilder.EmptyTagLookup(original) { @Override public Optional> get(final ResourceKey id) { return original.get(id); } }; } private static HolderLookup.RegistryLookup lookupFromMap( final ResourceKey> key, final Lifecycle lifecycle, final HolderOwner owner, final Map, Holder.Reference> entries ) { return new RegistrySetBuilder.EmptyTagRegistryLookup(owner) { @Override public ResourceKey> key() { return key; } @Override public Lifecycle registryLifecycle() { return lifecycle; } @Override public Optional> get(final ResourceKey id) { return Optional.ofNullable((Holder.Reference)entries.get(id)); } @Override public Stream> listElements() { return entries.values().stream(); } }; } public RegistrySetBuilder add( final ResourceKey> key, final Lifecycle lifecycle, final RegistrySetBuilder.RegistryBootstrap bootstrap ) { this.entries.add(new RegistrySetBuilder.RegistryStub<>(key, lifecycle, bootstrap)); return this; } public RegistrySetBuilder add(final ResourceKey> key, final RegistrySetBuilder.RegistryBootstrap bootstrap) { return this.add(key, Lifecycle.stable(), bootstrap); } private RegistrySetBuilder.BuildState createState(final RegistryAccess context) { RegistrySetBuilder.BuildState state = RegistrySetBuilder.BuildState.create(context, this.entries.stream().map(RegistrySetBuilder.RegistryStub::key)); this.entries.forEach(e -> e.apply(state)); return state; } private static HolderLookup.Provider buildProviderWithContext( final RegistrySetBuilder.UniversalOwner owner, final RegistryAccess context, final Stream> newRegistries ) { record Entry(HolderLookup.RegistryLookup lookup, RegistryOps.RegistryInfo opsInfo) { public static Entry createForContextRegistry(final HolderLookup.RegistryLookup registryLookup) { return new Entry<>( new RegistrySetBuilder.EmptyTagLookupWrapper<>(registryLookup, registryLookup), RegistryOps.RegistryInfo.fromRegistryLookup(registryLookup) ); } public static Entry createForNewRegistry(final RegistrySetBuilder.UniversalOwner owner, final HolderLookup.RegistryLookup registryLookup) { return new Entry<>( new RegistrySetBuilder.EmptyTagLookupWrapper<>(owner.cast(), registryLookup), new RegistryOps.RegistryInfo<>(owner.cast(), registryLookup, registryLookup.registryLifecycle()) ); } } final Map>, Entry> lookups = new HashMap(); context.registries().forEach(contextRegistry -> lookups.put(contextRegistry.key(), Entry.createForContextRegistry(contextRegistry.value()))); newRegistries.forEach(newRegistry -> lookups.put(newRegistry.key(), Entry.createForNewRegistry(owner, newRegistry))); return new HolderLookup.Provider() { @Override public Stream>> listRegistryKeys() { return lookups.keySet().stream(); } private Optional> getEntry(final ResourceKey> key) { return Optional.ofNullable((Entry)lookups.get(key)); } @Override public Optional> lookup(final ResourceKey> key) { return this.getEntry(key).map(Entry::lookup); } @Override public RegistryOps createSerializationContext(final DynamicOps parent) { return RegistryOps.create(parent, new RegistryOps.RegistryInfoLookup() { { Objects.requireNonNull(); } @Override public Optional> lookup(final ResourceKey> registryKey) { return getEntry(registryKey).map(Entry::opsInfo); } }); } }; } public HolderLookup.Provider build(final RegistryAccess context) { RegistrySetBuilder.BuildState state = this.createState(context); Stream> newRegistries = this.entries.stream().map(stub -> stub.collectRegisteredValues(state).buildAsLookup(state.owner)); HolderLookup.Provider result = buildProviderWithContext(state.owner, context, newRegistries); state.reportNotCollectedHolders(); state.reportUnclaimedRegisteredValues(); state.throwOnError(); return result; } private HolderLookup.Provider createLazyFullPatchedRegistries( final RegistryAccess context, final HolderLookup.Provider fallbackProvider, final Cloner.Factory clonerFactory, final Map>, RegistrySetBuilder.RegistryContents> newRegistries, final HolderLookup.Provider patchOnlyRegistries ) { RegistrySetBuilder.UniversalOwner fullPatchedOwner = new RegistrySetBuilder.UniversalOwner(); MutableObject resultReference = new MutableObject<>(); List> lazyFullRegistries = (List>)newRegistries.keySet() .stream() .map( registryKey -> this.createLazyFullPatchedRegistries(fullPatchedOwner, clonerFactory, registryKey, patchOnlyRegistries, fallbackProvider, resultReference) ) .collect(Collectors.toUnmodifiableList()); HolderLookup.Provider result = buildProviderWithContext(fullPatchedOwner, context, lazyFullRegistries.stream()); resultReference.setValue(result); return result; } private HolderLookup.RegistryLookup createLazyFullPatchedRegistries( final HolderOwner owner, final Cloner.Factory clonerFactory, final ResourceKey> registryKey, final HolderLookup.Provider patchProvider, final HolderLookup.Provider fallbackProvider, final MutableObject targetProvider ) { Cloner cloner = clonerFactory.cloner(registryKey); if (cloner == null) { throw new NullPointerException("No cloner for " + registryKey.identifier()); } else { Map, Holder.Reference> entries = new HashMap(); HolderLookup.RegistryLookup patchContents = patchProvider.lookupOrThrow(registryKey); patchContents.listElements().forEach(elementHolder -> { ResourceKey elementKey = elementHolder.key(); RegistrySetBuilder.LazyHolder holder = new RegistrySetBuilder.LazyHolder<>(owner, elementKey); holder.supplier = () -> cloner.clone((T)elementHolder.value(), patchProvider, targetProvider.get()); entries.put(elementKey, holder); }); HolderLookup.RegistryLookup fallbackContents = fallbackProvider.lookupOrThrow(registryKey); fallbackContents.listElements().forEach(elementHolder -> { ResourceKey elementKey = elementHolder.key(); entries.computeIfAbsent(elementKey, key -> { RegistrySetBuilder.LazyHolder holder = new RegistrySetBuilder.LazyHolder<>(owner, elementKey); holder.supplier = () -> cloner.clone((T)elementHolder.value(), fallbackProvider, targetProvider.get()); return holder; }); }); Lifecycle lifecycle = patchContents.registryLifecycle().add(fallbackContents.registryLifecycle()); return lookupFromMap(registryKey, lifecycle, owner, entries); } } public RegistrySetBuilder.PatchedRegistries buildPatch( final RegistryAccess context, final HolderLookup.Provider fallbackProvider, final Cloner.Factory clonerFactory ) { RegistrySetBuilder.BuildState state = this.createState(context); Map>, RegistrySetBuilder.RegistryContents> newRegistries = new HashMap(); this.entries.stream().map(stub -> stub.collectRegisteredValues(state)).forEach(e -> newRegistries.put(e.key, e)); Set>> contextRegistries = (Set>>)context.listRegistryKeys() .collect(Collectors.toUnmodifiableSet()); fallbackProvider.listRegistryKeys() .filter(k -> !contextRegistries.contains(k)) .forEach(resourceKey -> newRegistries.putIfAbsent(resourceKey, new RegistrySetBuilder.RegistryContents(resourceKey, Lifecycle.stable(), Map.of()))); Stream> dynamicRegistries = newRegistries.values() .stream() .map(registryContents -> registryContents.buildAsLookup(state.owner)); HolderLookup.Provider patchOnlyRegistries = buildProviderWithContext(state.owner, context, dynamicRegistries); state.reportUnclaimedRegisteredValues(); state.throwOnError(); HolderLookup.Provider fullPatchedRegistries = this.createLazyFullPatchedRegistries( context, fallbackProvider, clonerFactory, newRegistries, patchOnlyRegistries ); return new RegistrySetBuilder.PatchedRegistries(fullPatchedRegistries, patchOnlyRegistries); } private record BuildState( RegistrySetBuilder.UniversalOwner owner, RegistrySetBuilder.UniversalLookup lookup, Map> registries, Map, RegistrySetBuilder.RegisteredValue> registeredValues, List errors ) { public static RegistrySetBuilder.BuildState create(final RegistryAccess context, final Stream>> newRegistries) { RegistrySetBuilder.UniversalOwner owner = new RegistrySetBuilder.UniversalOwner(); List errors = new ArrayList(); RegistrySetBuilder.UniversalLookup lookup = new RegistrySetBuilder.UniversalLookup(owner); Builder> registries = ImmutableMap.builder(); context.registries() .forEach(contextRegistry -> registries.put(contextRegistry.key().identifier(), RegistrySetBuilder.wrapContextLookup(contextRegistry.value()))); newRegistries.forEach(newRegistry -> registries.put(newRegistry.identifier(), lookup)); return new RegistrySetBuilder.BuildState(owner, lookup, registries.build(), new HashMap(), errors); } public BootstrapContext bootstrapContext() { return new BootstrapContext() { { Objects.requireNonNull(BuildState.this); } @Override public Holder.Reference register(final ResourceKey key, final T value, final Lifecycle lifecycle) { RegistrySetBuilder.RegisteredValue previousValue = (RegistrySetBuilder.RegisteredValue)BuildState.this.registeredValues .put(key, new RegistrySetBuilder.RegisteredValue(value, lifecycle)); if (previousValue != null) { BuildState.this.errors.add(new IllegalStateException("Duplicate registration for " + key + ", new=" + value + ", old=" + previousValue.value)); } return BuildState.this.lookup.getOrCreate(key); } @Override public HolderGetter lookup(final ResourceKey> key) { return (HolderGetter)BuildState.this.registries.getOrDefault(key.identifier(), BuildState.this.lookup); } }; } public void reportUnclaimedRegisteredValues() { this.registeredValues .forEach((key, registeredValue) -> this.errors.add(new IllegalStateException("Orpaned value " + registeredValue.value + " for key " + key))); } public void reportNotCollectedHolders() { for (ResourceKey key : this.lookup.holders.keySet()) { this.errors.add(new IllegalStateException("Unreferenced key: " + key)); } } public void throwOnError() { if (!this.errors.isEmpty()) { IllegalStateException result = new IllegalStateException("Errors during registry creation"); for (RuntimeException error : this.errors) { result.addSuppressed(error); } throw result; } } } private abstract static class EmptyTagLookup implements HolderGetter { protected final HolderOwner owner; protected EmptyTagLookup(final HolderOwner owner) { this.owner = owner; } @Override public Optional> get(final TagKey id) { return Optional.of(HolderSet.emptyNamed(this.owner, id)); } } private static class EmptyTagLookupWrapper extends RegistrySetBuilder.EmptyTagRegistryLookup implements HolderLookup.RegistryLookup.Delegate { private final HolderLookup.RegistryLookup parent; private EmptyTagLookupWrapper(final HolderOwner owner, final HolderLookup.RegistryLookup parent) { super(owner); this.parent = parent; } @Override public HolderLookup.RegistryLookup parent() { return this.parent; } } private abstract static class EmptyTagRegistryLookup extends RegistrySetBuilder.EmptyTagLookup implements HolderLookup.RegistryLookup { protected EmptyTagRegistryLookup(final HolderOwner owner) { super(owner); } @Override public Stream> listTags() { throw new UnsupportedOperationException("Tags are not available in datagen"); } } private static class LazyHolder extends Holder.Reference { @Nullable private Supplier supplier; protected LazyHolder(final HolderOwner owner, @Nullable final ResourceKey key) { super(Holder.Reference.Type.STAND_ALONE, owner, key, null); } @Override protected void bindValue(final T value) { super.bindValue(value); this.supplier = null; } @Override public T value() { if (this.supplier != null) { this.bindValue((T)this.supplier.get()); } return super.value(); } } public record PatchedRegistries(HolderLookup.Provider full, HolderLookup.Provider patches) { } private record RegisteredValue(T value, Lifecycle lifecycle) { } @FunctionalInterface public interface RegistryBootstrap { void run(BootstrapContext registry); } private record RegistryContents( ResourceKey> key, Lifecycle lifecycle, Map, RegistrySetBuilder.ValueAndHolder> values ) { public HolderLookup.RegistryLookup buildAsLookup(final RegistrySetBuilder.UniversalOwner owner) { Map, Holder.Reference> entries = (Map, Holder.Reference>)this.values .entrySet() .stream() .collect( Collectors.toUnmodifiableMap( java.util.Map.Entry::getKey, e -> { RegistrySetBuilder.ValueAndHolder entry = (RegistrySetBuilder.ValueAndHolder)e.getValue(); Holder.Reference holder = (Holder.Reference)entry.holder() .orElseGet(() -> Holder.Reference.createStandAlone(owner.cast(), (ResourceKey)e.getKey())); holder.bindValue(entry.value().value()); return holder; } ) ); return RegistrySetBuilder.lookupFromMap(this.key, this.lifecycle, owner.cast(), entries); } } private record RegistryStub(ResourceKey> key, Lifecycle lifecycle, RegistrySetBuilder.RegistryBootstrap bootstrap) { private void apply(final RegistrySetBuilder.BuildState state) { this.bootstrap.run(state.bootstrapContext()); } public RegistrySetBuilder.RegistryContents collectRegisteredValues(final RegistrySetBuilder.BuildState state) { Map, RegistrySetBuilder.ValueAndHolder> result = new HashMap(); Iterator, RegistrySetBuilder.RegisteredValue>> iterator = state.registeredValues.entrySet().iterator(); while (iterator.hasNext()) { java.util.Map.Entry, RegistrySetBuilder.RegisteredValue> entry = (java.util.Map.Entry, RegistrySetBuilder.RegisteredValue>)iterator.next(); ResourceKey key = (ResourceKey)entry.getKey(); if (key.isFor(this.key)) { RegistrySetBuilder.RegisteredValue value = (RegistrySetBuilder.RegisteredValue)entry.getValue(); Holder.Reference holder = (Holder.Reference)state.lookup.holders.remove(key); result.put(key, new RegistrySetBuilder.ValueAndHolder<>(value, Optional.ofNullable(holder))); iterator.remove(); } } return new RegistrySetBuilder.RegistryContents<>(this.key, this.lifecycle, result); } } private static class UniversalLookup extends RegistrySetBuilder.EmptyTagLookup { private final Map, Holder.Reference> holders = new HashMap(); public UniversalLookup(final HolderOwner owner) { super(owner); } @Override public Optional> get(final ResourceKey id) { return Optional.of(this.getOrCreate(id)); } private Holder.Reference getOrCreate(final ResourceKey id) { return (Holder.Reference)this.holders.computeIfAbsent(id, k -> Holder.Reference.createStandAlone(this.owner, k)); } } private static class UniversalOwner implements HolderOwner { public HolderOwner cast() { return this; } } private record ValueAndHolder(RegistrySetBuilder.RegisteredValue value, Optional> holder) { } }