Add DeferredRegistryData to help with generating data driven registry entries.

Revive GameTest system and make all but one Forge Test mod pass.
This commit is contained in:
LexManos committed 2025-03-27 20:52:35 -07:00
1 parent b4b6e48a7a
commit 0f3b8ee830
102 files changed
+1229 -469

No files matched your search

@@ -27,7 +27,6 @@ import net.minecraft.client.gui.components.ChatComponent;
import net.minecraft.client.gui.components.LerpingBossEvent;
import net.minecraft.client.gui.screens.ConfirmScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner;
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
@@ -50,7 +49,6 @@ import net.minecraft.client.renderer.entity.state.HumanoidRenderState;
import net.minecraft.client.renderer.texture.SpriteContents;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.client.resources.metadata.animation.FrameSize;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelBakery;
@@ -132,7 +130,6 @@ import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.IExtensionPoint;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.VersionChecker;
import net.minecraftforge.network.NetworkContext;
import net.minecraftforge.network.NetworkInitialization;
import net.minecraftforge.network.NetworkRegistry;
@@ -293,50 +290,6 @@ public class ForgeHooksClient {
}
}
/**
* Initialization of Forge Renderers.
*/
static {
//FluidRegistry.renderIdFluid = RenderingRegistry.getNextAvailableRenderId();
//RenderingRegistry.registerBlockHandler(RenderBlockFluid.instance);
}
private static VersionChecker.Status getForgeVersionStatus() {
final class LazyInit {
private static final VersionChecker.Status INSTANCE = ForgeVersion.getStatus();
static {
forgeStatusLine = switch (INSTANCE) {
// case FAILED -> " Version check failed";
// case UP_TO_DATE -> "Forge up to date";
// case AHEAD -> "Using non-recommended Forge build, issues may arise.";
case OUTDATED, BETA_OUTDATED -> I18n.get("forge.update.newversion", ForgeVersion.getTarget());
default -> null;
};
}
private LazyInit() {}
}
return LazyInit.INSTANCE;
}
// NO-OP method, kept for bin-compat. Used in TitleScreen.java
@Deprecated(forRemoval = true, since = "1.21.4")
public static void renderMainMenu(TitleScreen gui, GuiGraphics graphics, Font font, int width, int height, int alpha) {
/*
VersionChecker.Status status = getForgeVersionStatus();
if (status == VersionChecker.Status.BETA || status == VersionChecker.Status.BETA_OUTDATED) {
// Render a warning at the top of the screen
Component line = Component.translatable("forge.update.beta.1", ChatFormatting.RED, ChatFormatting.RESET).withStyle(ChatFormatting.RED);
graphics.drawCenteredString(font, line, width / 2, 4, 0xFFFFFF | alpha);
line = Component.translatable("forge.update.beta.2");
graphics.drawCenteredString(font, line, width / 2, 4 + (font.lineHeight + 1), 0xFFFFFF | alpha);
}
*/
}
public static String forgeStatusLine;
@Nullable
@@ -412,7 +412,8 @@ public enum ForgeRenderTypes {
/**
* Use {@link #TEXT_INTENSITY_SEE_THROUGH} instead.
*/
@Deprecated
@SuppressWarnings("unused")
@Deprecated(forRemoval = true, since = "1.21.5")
public static Function<ResourceLocation, RenderType> TEXT_INTENSITY_SEETHROUGH = TEXT_INTENSITY_SEE_THROUGH;
private static RenderType getTextIntensitySeeThrough(ResourceLocation locationIn) {
return create(
@@ -18,8 +18,6 @@ import java.util.stream.Collectors;
import net.minecraftforge.client.gui.widget.ScrollPanel;
import org.apache.commons.lang3.tuple.Pair;
import com.mojang.blaze3d.vertex.Tesselator;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
@@ -22,6 +22,18 @@ public class DatapackBuiltinEntriesProvider extends RegistriesDatapackGenerator
private final CompletableFuture<HolderLookup.Provider> fullRegistries;
/**
* Constructs a new datapack provider which generates all registry objects
* from the provided mods using the holder.
*
* @param output the target directory of the data generator
* @param registries a future of patched registries
* @param modId The mod id to generate the dynamic registry objects of
*/
public DatapackBuiltinEntriesProvider(PackOutput output, CompletableFuture<RegistrySetBuilder.PatchedRegistries> registries, String modId) {
this(output, registries, Set.of(modId));
}
/**
* Constructs a new datapack provider which generates all registry objects
* from the provided mods using the holder.
@@ -35,6 +47,20 @@ public class DatapackBuiltinEntriesProvider extends RegistriesDatapackGenerator
this.fullRegistries = registries.thenApply(RegistrySetBuilder.PatchedRegistries::full);
}
/**
* Constructs a new datapack provider which generates all registry objects
* from the provided mods using the holder. All entries that need to be
* bootstrapped are provided within the {@link RegistrySetBuilder}.
*
* @param output the target directory of the data generator
* @param registries a future of a lookup for registries and their objects
* @param registryBuilder a builder containing the dynamic registry objects added by this provider
* @param modId The mod id to generate the dynamic registry objects of
*/
public DatapackBuiltinEntriesProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, RegistrySetBuilder registryBuilder, String modId) {
this(output, registries, registryBuilder, Set.of(modId));
}
/**
* Constructs a new datapack provider which generates all registry objects
* from the provided mods using the holder. All entries that need to be
@@ -87,7 +87,7 @@ public interface IForgeBlockEntity extends ICapabilityProvider {
bb = AABB.encapsulatingFullBlocks(pos.offset(-1, 0, -1), pos.offset(1, 0, 1));
} else if (block == Blocks.STRUCTURE_BLOCK) {
bb = INFINITE_EXTENT_AABB;
} else if (block != null && block != Blocks.BEACON) {
} else if (block != null && block != Blocks.BEACON && block != Blocks.TEST_INSTANCE_BLOCK) {
AABB cbb = null;
try {
VoxelShape collisionShape = state.getCollisionShape(self().getLevel(), pos);
@@ -0,0 +1,25 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.common.extensions;
import com.mojang.serialization.Lifecycle;
import net.minecraft.core.RegistrySetBuilder;
import net.minecraftforge.registries.DeferredRegisterData;
public interface IForgeRegistrySetBuilder {
private RegistrySetBuilder self() {
return (RegistrySetBuilder)this;
}
default <T> RegistrySetBuilder add(DeferredRegisterData<T> dr) {
return self().add(dr.getRegistryKey(), dr);
}
default <T> RegistrySetBuilder add(DeferredRegisterData<T> dr, Lifecycle lifecycle) {
return self().add(dr.getRegistryKey(), lifecycle, dr);
}
}
@@ -6,46 +6,31 @@
package net.minecraftforge.gametest;
import net.minecraft.SharedConstants;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.gametest.framework.GameTestEnvironments;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.gametest.framework.GameTestInstance;
import net.minecraft.gametest.framework.TestData;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import net.minecraftforge.forgespi.language.ModFileScanData;
import net.minecraftforge.forgespi.language.ModFileScanData.AnnotationData;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Type;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.function.Consumer;
/**
* Internal class used to glue mods into the game test framework.
* Modders should use the supplied annotations and DeferredRegister
*/
@SuppressWarnings("unused")
@ApiStatus.Internal
public class ForgeGameTestHooks {
private static boolean registeredGametests = false;
private static final Logger LOGGER = LogManager.getLogger();
private static final Type GAME_TEST_HOLDER = Type.getType(GameTestHolder.class);
private static MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
public static boolean isGametestEnabled() {
return !FMLLoader.isProduction() && (SharedConstants.IS_RUNNING_IN_IDE || isGametestServer() || Boolean.getBoolean("forge.enableGameTest"));
@@ -55,78 +40,45 @@ public class ForgeGameTestHooks {
return !FMLLoader.isProduction() && Boolean.getBoolean("forge.gameTestServer");
}
/*
@SuppressWarnings("deprecation")
public static void registerGametests() {
if (!registeredGametests && isGametestEnabled() && ModLoader.isLoadingStateValid()) {
Set<String> enabledNamespaces = getEnabledNamespaces();
LOGGER.info("Enabled Gametest Namespaces: {}", enabledNamespaces);
Set<Method> gameTestMethods = new HashSet<>();
RegisterGameTestsEvent event = new RegisterGameTestsEvent(gameTestMethods);
ModLoader.get().postEvent(event);
ModList.get().getAllScanData().stream()
.map(ModFileScanData::getAnnotations)
.flatMap(Collection::stream)
.filter(a -> GAME_TEST_HOLDER.equals(a.annotationType()))
.forEach(a -> addGameTestMethods(a, gameTestMethods));
for (Method gameTestMethod : gameTestMethods) {
GameTestRegistry.register(gameTestMethod, enabledNamespaces);
}
registeredGametests = true;
}
}
private static Set<String> getEnabledNamespaces() {
String enabledNamespacesStr = System.getProperty("forge.enabledGameTestNamespaces");
if (enabledNamespacesStr == null)
return Set.of();
return Arrays.stream(enabledNamespacesStr.split(",")).filter(s -> !s.isBlank()).collect(Collectors.toUnmodifiableSet());
}
private static void addGameTestMethods(AnnotationData annotationData, Set<Method> gameTestMethods) {
try {
Class<?> clazz = Class.forName(annotationData.clazz().getClassName(), true, ForgeGameTestHooks.class.getClassLoader());
gameTestMethods.addAll(Arrays.asList(clazz.getDeclaredMethods()));
} catch (ClassNotFoundException e) {
// Should not be possible
throw new RuntimeException(e);
}
}
public static String getTestName(Method method, GameTest meta) {
var name = method.getName().toLowerCase(Locale.ENGLISH);
return getPrefixed(method, name);
}
private static String getPrefixed(Method method, String name) {
var prefix = getPrefix(method);
return prefix == null ? name : prefix + '.' + name;
}
@Nullable
private static String getPrefix(Method method) {
private static boolean shouldPrefix(Method method) {
var cls = method.getDeclaringClass();
var shouldPrefix = !method.isAnnotationPresent(GameTestDontPrefix.class) &&
!cls.isAnnotationPresent(GameTestDontPrefix.class);
if (!shouldPrefix)
return null;
return !method.isAnnotationPresent(GameTestDontPrefix.class) &&
!cls.isAnnotationPresent(GameTestDontPrefix.class);
}
return getPrefix(cls);
private static String snake(String input) {
var result = new StringBuilder();
for (char c : input.toCharArray()) {
if (Character.isUpperCase(c)) {
if (!result.isEmpty())
result.append('_');
result.append(Character.toLowerCase(c));
} else
result.append(c);
}
return result.toString();
}
private static String getPrefix(Class<?> cls) {
var prefix = cls.getAnnotation(GameTestPrefix.class);
if (prefix != null)
return prefix.value();
return prefix.value() + '/';
var holder = cls.getAnnotation(GameTestHolder.class);
var mod = cls.getAnnotation(Mod.class);
if (mod != null) {
var holder = cls.getAnnotation(GameTestNamespace.class);
if (holder != null && !holder.value().isEmpty())
return mod.value() + '/';
}
return cls.getSimpleName().toLowerCase(Locale.ENGLISH);
}
private static String getNamespace(Class<?> cls) {
var holder = cls.getAnnotation(GameTestNamespace.class);
if (holder != null && !holder.value().isEmpty())
return holder.value();
@@ -134,21 +86,32 @@ public class ForgeGameTestHooks {
if (mod != null)
return mod.value();
return cls.getSimpleName().toLowerCase(Locale.ENGLISH);
throw new IllegalArgumentException("Could not find modid for " + cls.getName());
}
private static ResourceLocation key(String namespace, String path) {
return path.indexOf(':') == -1 ? ResourceLocation.parse(path) : ResourceLocation.fromNamespaceAndPath(namespace, path);
private static ResourceLocation key(String namespace, String prefix, String path) {
if (path.indexOf(':') != -1) {
var rl = ResourceLocation.parse(path);
if (!prefix.isEmpty())
return rl.withPrefix(prefix);
return rl;
}
return ResourceLocation.fromNamespaceAndPath(namespace, prefix + path);
}
public static Map<ResourceLocation, GameTestInstance> gatherTests(Class<?> root) {
String prefix = getPrefix(root);
var seen = new HashSet<ResourceLocation>();
public record TestReference(Consumer<GameTestHelper> consumer, TestData<ResourceLocation> data) {}
public static Map<ResourceLocation, TestReference> gatherTests(Class<?> root, Object instance) {
var ret = new HashMap<ResourceLocation, TestReference>();
var seen = new HashSet<String>();
String class_prefix = getPrefix(root);
String namespace = getNamespace(root);
Class<?> cls = root;
while (cls != Object.class) {
for (var method : cls.getDeclaredMethods()) {
if (cls != root && Modifier.isStatic(method.getModifiers()))
if (!Modifier.isPublic(method.getModifiers()) || (cls != root && Modifier.isStatic(method.getModifiers())))
continue;
var gametest = method.getAnnotation(GameTest.class);
@@ -160,40 +123,73 @@ public class ForgeGameTestHooks {
if (method.getParameterCount() != 1 || method.getParameterTypes()[0] != GameTestHelper.class)
throw new IllegalStateException("Invalid @GameTest function " + owner + " incorrect arguments");
var testName = key(prefix, gametest.name().isEmpty() ? method.getName() : gametest.name());
if (!seen.add(testName)) {
if (!Modifier.isStatic(method.getModifiers()))
continue;
throw new IllegalStateException("Failed to register test, already seen " + testName + ", for " + owner);
}
// We don't want parent methods
if (!seen.add(method.getName()))
continue;
if (gametest.type() == GameTest.Type.FUNCTION) {
var funcName = key(prefix, gametest.function());
var func = BuiltInRegistries.TEST_FUNCTION.get(funcName).orElse(null);
var prefix = shouldPrefix(method) ? class_prefix : "";
var name = key(namespace, prefix, gametest.name().isEmpty() ? snake(method.getName()) : gametest.name());
if (func == null)
throw new IllegalStateException("Could not find referenced function `" + funcName + "` for " + owner);
var structure = GameTest.DEFAULT_STRUCTURE.equals(gametest.structure())
? ResourceLocation.parse(gametest.structure())
: key(namespace, "", gametest.structure());
var data = getTestData(prefix, owner, gametest.data());
var env = GameTestEnvironments.DEFAULT.equals(gametest.environment())
? ResourceLocation.withDefaultNamespace(gametest.environment())
: key(namespace, "", gametest.environment());
var data = new TestData<ResourceLocation>(
env,
structure,
gametest.maxTicks(),
gametest.setupTicks(),
gametest.required(),
gametest.rotation(),
gametest.manualOnly(),
gametest.maxAttempts(),
gametest.requiredSuccesses(),
gametest.skyAccess()
);
} else if (gametest.type() == GameTest.Type.BLOCK) {
MethodHandle handle = unreflect(method, owner);
Consumer<GameTestHelper> func;
if (Modifier.isStatic(method.getModifiers())) {
func = ctx -> {
try {
handle.invoke(ctx);
} catch (Throwable e) {
sneakyThrow(e);
}
};
} else {
throw new IllegalStateException("Could not determine GameTest type for " + root.getName() + " -> " + gametest.type());
func = ctx -> {
try {
handle.invoke(instance, ctx);
} catch (Throwable e) {
sneakyThrow(e);
}
};
}
ret.put(name, new TestReference(func, data));
}
cls = cls.getSuperclass();
}
return ret;
}
private static MethodHandle unreflect(Method method, String owner) {
try {
return LOOKUP.unreflect(method);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Failed to unreflect " + owner, e);
}
}
private static TestData<?> getTestData(String prefix, String owner, GameTest.Data data) {
var envName = key(prefix, data.environment());
var env = BuiltInRegistries.TEST_ENVIRONMENT
@SuppressWarnings("unchecked")
private static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
throw (E)e;
}
*/
}
@@ -6,39 +6,23 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import net.minecraft.gametest.framework.GameTestEnvironments;
import net.minecraft.world.level.block.Rotation;
@Retention(RUNTIME)
@Target(METHOD)
public @interface GameTest {
String name() default "";
String function() default "";
Type type() default Type.BLOCK;
Data data() default @Data;
int rotationSteps() default 0;
public enum Type {
BLOCK,
FUNCTION;
}
/**
* Annotation definitions for {@link net.minecraft.gametest.framework.TestData}
*/
public @interface Data {
String environment() default "";
String structure() default "forge:empty3x3";
int maxTicks() default Integer.MAX_VALUE;
int setupTicks() default 0;
boolean required() default false;
Rotation rotation() default Rotation.NONE;
boolean manualOnly() default false;
int maxAttempts() default 1;
int requiredSuccesses() default 1;
boolean skyAccess() default false;
}
static final String DEFAULT_STRUCTURE = "forge:empty3x3x3";
String environment() default GameTestEnvironments.DEFAULT;
String structure() default DEFAULT_STRUCTURE;
int maxTicks() default 100;
int setupTicks() default 0;
boolean required() default true;
Rotation rotation() default Rotation.NONE;
boolean manualOnly() default false;
int maxAttempts() default 1;
int requiredSuccesses() default 1;
boolean skyAccess() default false;
}
@@ -1,46 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.gametest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a class as containing game tests that should be registered automatically.
* All methods annotated with {@link GameTest} or {@link GameTestGenerator} will be registered.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GameTestHolder {
/**
* The default batch for all tests in this class that do not manually specify one.
* It it recommended that this includes your mod id.
* <br/>
* This is also used when filtering out which batches to run during automation.
* the filters will be tested against the prefix of the batch name. So it is
* recommended to be structured in a logical fashion.
* <pre>
* Examples:
* forge.tags.items
* forge.tags.blocks
* forge.global_loot_modifiers.block_drops
* forge.global_loot_modifiers.tool_filter
* </pre>
*
* The filter 'forge.global_loot_modifiers' would execute both the block_drops and tool_filter batches.
* <p>
* This will default to your modid if it is on the same class that is annotated with {@link net.minecraftforge.fml.common.Mod @Mod}
*/
String value() default "";
/**
* Used as the default namespace to use for {@link GameTest#template() templates} for any game tests in the class.
* Defaults to the modid if in a class with @Mod or else defaults to "minecraft"
*/
String namespace() default "";
}
@@ -0,0 +1,28 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.gametest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a class as containing game tests that should be registered automatically.
* All methods annotated with {@link GameTest} or {@link GameTestGenerator} will be registered.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GameTestNamespace {
/**
* The default namespace for all tests in this class that do not manually specify one.
* It it recommended that this is your modid, but if you have multiple test mods that
* you want to group together you can use this to do so.
* <p>
* This will default to your modid if it is on the same class that is annotated with {@link net.minecraftforge.fml.common.Mod @Mod}
*/
String value() default "";
}
@@ -0,0 +1,309 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.registries;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistrySetBuilder.RegistryBootstrap;
import net.minecraft.data.worldgen.BootstrapContext;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraftforge.common.extensions.IForgeRegistrySetBuilder;
import org.jetbrains.annotations.NotNull;
import com.mojang.serialization.Lifecycle;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Utility class to help with managing registry entries.
* Maintains a list of all suppliers for entries and registers them during the proper Register event.
* Suppliers should return NEW instances every time.
*
*Example Usage:
*<pre>{@code
* public static final DeferredRegisterData<ConfiguredFeature<?, ?>> CONFIGURED_FEATURES = DeferredRegisterData.create(Registries.CONFIGURED_FEATURE, MOD_ID);
* private static final RegistryObject<ConfiguredFeature<?, ?>> CONFIGURED = CONFIGURED_FEATURES.register("configured", () ->
* new ConfiguredFeature<>(Feature.NO_OP, NoneFeatureConfiguration.INSTANCE)
* );
*
* public static final DeferredRegisterData<PlacedFeature> PLACED_FEATURES = DeferredRegisterData.create(Registries.PLACED_FEATURE, MOD_ID);
* private static final RegistryObject<PlacedFeature> PLACED = PLACED_FEATURES.register("placed", () ->
* new PlacedFeature(CONFIGURED.getHolder().orElseThrow(), List.of())
* );
*
* public static final DeferredRegisterData<BiomeModifier> BIOME_MODIFIERS = DeferredRegisterData.create(ForgeRegistries.Keys.BIOME_MODIFIERS, MOD_ID);
* @SuppressWarnings("unused")
* private static final RegistryObject<BiomeModifier> MODIFIER = BIOME_MODIFIERS.register("modifier", ctx -> {
* return new ForgeBiomeModifiers.AddFeaturesBiomeModifier(
* ctx.lookup(Registries.BIOME).getOrThrow(BiomeTags.IS_OVERWORLD),
* HolderSet.direct(PLACED.getHolder().orElseThrow()),
* GenerationStep.Decoration.UNDERGROUND_ORES
* );
* });
*
* protected void generateDataRegistries(GatherDataEvent event) {
* var registrySet = VanillaRegistries.builder()
* .add(CONFIGURED_FEATURES)
* .add(PLACED_FEATURES)
* .add(BIOME_MODIFIERS);
*
* event.getGenerator().addProvider(event.includeServer(), new DatapackBuiltinEntriesProvider(
* gen.getPackOutput(), event.getLookupProvider(), registrySet, modid)
* );
* }
*
*}</pre>
*
* @param <T> The base registry type
*/
public class DeferredRegisterData<T> implements RegistryBootstrap<T> {
/**
* DeferredRegister factory for data driven registries. This will not create objects during the normal registry events.
* This is meant to be used as helper for creating a {@link RegistrySetBuilder}, and thus should <string>NOT</strong>
* be registered to and {@link EventBus}. But instead used with {@link IForgeRegistrySetBuilder#add(DeferredRegister)}.
*
* @param registryKey the key of the registry to reference.
* @param modid the namespace for all objects registered to this DeferredRegister
*/
public static <B> DeferredRegisterData.Builder<B> builder(ResourceKey<? extends Registry<B>> registryKey, String modid) {
requireNonNull("registryKey", registryKey);
requireNonNull("modid", modid);
return new Builder<>(registryKey, modid);
}
/**
* DeferredRegister factory for data driven registries. This will not create objects during the normal registry events.
* This is meant to be used as helper for creating a {@link RegistrySetBuilder}, and thus should <string>NOT</strong>
* be registered to and {@link EventBus}. But instead used with {@link IForgeRegistrySetBuilder#add(DeferredRegister)}.
*
* @param registryKey the key of the registry to reference.
* @param modid the namespace for all objects registered to this DeferredRegister
*
* @return A built instance using the default values for the builder. Equivalent to {@code builder(registryKey, modid).build()}
*/
public static <B> DeferredRegisterData<B> create(ResourceKey<? extends Registry<B>> registryKey, String modid) {
return new Builder<>(registryKey, modid).build();
}
public static class Builder<T> {
private final ResourceKey<? extends Registry<T>> registryKey;
private final String modid;
private Lifecycle lifecycle = Lifecycle.stable();
private Builder(ResourceKey<? extends Registry<T>> registryKey, String modid) {
this.registryKey = registryKey;
this.modid = modid;
}
/**
* By default will register objects as {@link Lifecycle#stable()}
*/
public Builder<T> lifecycle(Lifecycle value) {
this.lifecycle = value;
return this;
}
public DeferredRegisterData<T> build() {
return new DeferredRegisterData<>(this);
}
}
private final ResourceKey<? extends Registry<T>> registryKey;
private final String modid;
private final Lifecycle lifecycle;
private final Map<RegistryObject<T>, Function<BootstrapContext<T>, ? extends T>> entries = new LinkedHashMap<>();
private final Set<RegistryObject<T>> entriesView = Collections.unmodifiableSet(entries.keySet());
private boolean beenBuilt = false;
private DeferredRegisterData(Builder<T> builder) {
this.registryKey = builder.registryKey;
this.modid = builder.modid;
this.lifecycle = builder.lifecycle;
}
/**
* Creates a RegistryObject for the specified name, which will be filled when the {@link RegistrySetBuilder} is built.
* This is useful is you need references to entries added not using the {@link DeferredRegisterData} system.
*
* @param name The entry's name, it will automatically have the modid prefixed.
*/
public <I extends T> RegistryObject<I> register(final String name) {
return registerInternal(name, null);
}
/**
* Adds a new supplier to the list of entries to be registered, and returns a RegistryObject that will be populated with the created entry automatically.
* If you need context from other data registries, use {@link DeferredRegisterData#register(String, Function)}
*
* @param name The new entry's name, it will automatically have the modid prefixed.
* @param factory A factory for the new entry, it should return a new instance every time it is called.
* @return A RegistryObject that will be updated with when the entries in the registry change.
* @see #register(String, Supplier)
*/
public <I extends T> RegistryObject<I> register(final String name, final Supplier<? extends I> factory) {
return register(name, ctx -> factory.get());
}
/**
* Adds a new entry to be registered, the Function will be invoked with the {@link BootstrapContext} which allows lookup of other registry entries.
* Returns a RegistryObject that will be populated with the created entry automatically.
*
* @param name The new entry's name, it will automatically have the modid prefixed.
* @param factory A factory for the new entry, it should return a new instance every time it is called.
* @return A RegistryObject that will be updated with when the entries in the registry change.
*/
public <I extends T> RegistryObject<I> register(final String name, final Function<BootstrapContext<T>, ? extends I> factory) {
requireNonNull("factory", factory);
return registerInternal(name, factory);
}
@SuppressWarnings("unchecked")
private <I extends T> RegistryObject<I> registerInternal(final String name, final Function<BootstrapContext<T>, ? extends I> factory) {
if (beenBuilt)
throw new IllegalStateException("Cannot register new entries to DeferredRegister after it has been used. You should use this in a static context");
requireNonNull("name", name);
var key = ResourceLocation.fromNamespaceAndPath(modid, name);
RegistryObject<I> ret = new RegistryObject<>(key, this.registryKey, this.modid);
if (entries.containsKey((RegistryObject<T>)ret))
throw new IllegalArgumentException("Duplicate registration " + name);
entries.put((RegistryObject<T>)ret, factory);
return ret;
}
/**
* Creates a ResourceKey based on the current modid and provided path as the location and the registry name linked to this DeferredRegister.
* To control the namespace, use {@link #key(ResourceLocation)}.
*
* @see #key(ResourceLocation)
*/
@NotNull
public ResourceKey<T> key(@NotNull String path) {
requireNonNull("path", path);
return key(ResourceLocation.fromNamespaceAndPath(this.modid, path));
}
/**
* Creates a tag key based on the provided resource location and the registry name linked to this DeferredRegister.
* To use the current modid as the namespace, use {@link #createTagKey(String)}.
*
* @see #key(String)
*/
@NotNull
public ResourceKey<T> key(@NotNull ResourceLocation location) {
requireNonNull("location", location);
return ResourceKey.create(getRegistryKey(), location);
}
/**
* Creates a tag key based on the current modid and provided path as the location and the registry name linked to this DeferredRegister.
* To control the namespace, use {@link #createTagKey(ResourceLocation)}.
*
* @see #createTagKey(ResourceLocation)
* @see #createOptionalTagKey(String, Set)
*/
@NotNull
public TagKey<T> createTagKey(@NotNull String path) {
requireNonNull("path", path);
return createTagKey(ResourceLocation.fromNamespaceAndPath(this.modid, path));
}
/**
* Creates a tag key based on the provided resource location and the registry name linked to this DeferredRegister.
* To use the current modid as the namespace, use {@link #createTagKey(String)}.
*
* @see #createTagKey(String)
* @see #createOptionalTagKey(ResourceLocation, Set)
*/
@NotNull
public TagKey<T> createTagKey(@NotNull ResourceLocation location) {
requireNonNull("location", location);
return TagKey.create(this.registryKey, location);
}
/**
* @return The {@link Lifecycle} that objects are registered as.
*/
public Lifecycle getLifecycle() {
return this.lifecycle;
}
/**
* @return The unmodifiable view of registered entries. Useful for bulk operations on all values.
*/
public Collection<RegistryObject<T>> getEntries() {
return this.entriesView;
}
/**
* @return The registry key stored in this deferred register. Useful for creating new deferred registers based on an existing one.
*/
public ResourceKey<? extends Registry<T>> getRegistryKey() {
return this.registryKey;
}
/**
* @return The registry name stored in this deferred register. Useful for creating new deferred registers based on an existing one.
*/
@NotNull
public ResourceLocation getRegistryName() {
return this.registryKey.location();
}
private static <T> T requireNonNull(String name, T value) {
if (value == null)
throw new IllegalArgumentException(name + " can not be null");
return value;
}
/**
* Returns true if the key is registered to a valid object factory, useful for data generation knowing if this actually declares data.
*/
public boolean isDeclared(ResourceKey<T> key) {
for (var entry : this.entries.entrySet()) {
if (entry.getKey().getKey() == key)
return true;
}
return false;
}
/**
* Build this DeferredRegister using the supplied {@link BoostrapContext}. This is meant to be called by {@link RegistrySetBuilder} by adding this
* function to the builder using {@link RegistrySetBuilder#add(ResourceKey, RegistryBootstrap)} or {@link IForgeRegistrySetBuilder#add(DeferredRegisterData)}
*/
@Override
public void run(BootstrapContext<T> context) {
beenBuilt = true;
var lookup = context.lookup(this.registryKey);
for (var e : entries.entrySet()) {
var ro = e.getKey();
T value;
if (e.getValue() != null) {
value = e.getValue().apply(context);
context.register(ro.getKey(), value, this.lifecycle);
} else
value = lookup.get(ro.getKey()).map(Holder::get).orElse(null);
ro.updateReference(value, () -> lookup.getOrThrow(ro.getKey()));
}
}
}
@@ -175,6 +175,15 @@ public final class RegistryObject<T> implements Supplier<T> {
this.updateReference(registryName);
}
/** Internal constructor used for {@link DeferredRegisterData} */
RegistryObject(final ResourceLocation name, final ResourceKey<? extends Registry<? super T>> registry, final String modid) {
this.name = name;
@SuppressWarnings("unchecked")
var typed = (ResourceKey<Registry<T>>)registry;
this.key = ResourceKey.create(typed, name);
this.optionalRegistry = true;
}
/**
* Retrieves the wrapped object in the registry.
* This value will automatically be updated when the backing registry is updated.
@@ -265,6 +274,11 @@ public final class RegistryObject<T> implements Supplier<T> {
this.value = null;
}
void updateReference(T value, Supplier<Holder<T>> holder) {
this.value = value;
this.holder = holder;
}
private static boolean registryExists(ResourceLocation registryName) {
return RegistryManager.ACTIVE.getRegistry(registryName) != null
|| BuiltInRegistries.REGISTRY.containsKey(registryName);