2016-06-22 23:49:47 -04:00
/*
2022-04-14 03:42:00 +00:00
* Copyright (c) Forge Development LLC and contributors
2022-02-28 13:41:23 -08:00
* SPDX-License-Identifier: LGPL-2.1-only
2016-06-22 23:49:47 -04:00
*/
2017-06-19 15:02:18 -07:00
package net.minecraftforge.registries ;
2014-05-24 22:32:24 -04:00
2019-01-09 20:57:01 -08:00
import java.lang.annotation.ElementType ;
2014-05-24 22:32:24 -04:00
import java.lang.reflect.Field ;
2019-01-09 20:57:01 -08:00
import java.util.HashSet ;
2014-05-24 22:32:24 -04:00
import java.util.List ;
2019-01-09 20:57:01 -08:00
import java.util.Locale ;
2026-10-12 22:18:32 +02:00
import java.util.ArrayList ;
import java.util.HashMap ;
2014-05-24 22:32:24 -04:00
import java.util.Map ;
import java.util.Set ;
2019-01-16 12:28:13 -08:00
import java.util.function.Consumer ;
import java.util.function.Predicate ;
2014-09-22 22:01:24 -07:00
2021-07-17 00:08:49 -05:00
import net.minecraft.resources.ResourceLocation ;
2019-01-09 20:57:01 -08:00
import net.minecraftforge.fml.ModList ;
2017-06-20 18:39:55 -07:00
import net.minecraftforge.fml.common.Mod ;
2019-01-13 22:51:36 -05:00
import net.minecraftforge.forgespi.language.ModFileScanData ;
2014-09-22 22:01:24 -07:00
2014-05-24 22:32:24 -04:00
import com.google.common.collect.Maps ;
2018-09-06 04:39:01 -07:00
import org.apache.logging.log4j.LogManager ;
import org.apache.logging.log4j.Logger ;
2022-06-07 15:31:26 -05:00
import org.jetbrains.annotations.Nullable ;
2018-09-06 04:39:01 -07:00
import org.objectweb.asm.Opcodes ;
2019-01-09 20:57:01 -08:00
import org.objectweb.asm.Type ;
2018-09-06 04:39:01 -07:00
2014-05-24 22:32:24 -04:00
/**
* Internal registry for tracking {@link ObjectHolder} references
*/
2019-01-09 20:57:01 -08:00
public class ObjectHolderRegistry
2017-06-19 15:02:18 -07:00
{
2019-01-09 20:57:01 -08:00
/**
* Exposed to allow modders to register their own notification handlers.
* This runnable will be called after a registry snapshot has been injected and finalized.
* The internal list is backed by a HashSet so it is HIGHLY recommended you implement a proper equals
* and hashCode function to de-duplicate callers here.
* The default @ObjectHolder implementation uses the hashCode/equals for the field the annotation is on.
2026-10-12 22:18:32 +02:00
*
* @deprecated Use {{@link #addHandler(ResourceLocation, Runnable)} if you are only targeting a single registry
2019-01-09 20:57:01 -08:00
*/
2022-04-01 15:49:46 -05:00
public static synchronized void addHandler ( Consumer < Predicate < ResourceLocation >> ref )
2019-01-09 20:57:01 -08:00
{
objectHolders . add ( ref );
}
2026-10-12 22:18:32 +02:00
/**
* Registers a handler that is run after the named registry has been injected and finalized.
* Handlers are kept in a HashSet per registry, so the same instance is only ever registered once.
*/
public static synchronized void addHandler ( ResourceLocation registry , Runnable ref )
{
namedHandlers . computeIfAbsent ( registry , id -> new HashSet <> ()). add ( ref );
}
2019-01-09 20:57:01 -08:00
/**
* Removed the specified handler from the notification list.
*
* The internal list is backed by a hash set, and so proper hashCode and equals operations are required for success.
*
* The default @ObjectHolder implementation uses the hashCode/equals for the field the annotation is on.
*
* @return true if handler was matched and removed.
*/
2022-04-01 15:49:46 -05:00
public static synchronized boolean removeHandler ( Consumer < Predicate < ResourceLocation >> ref )
2019-01-09 20:57:01 -08:00
{
return objectHolders . remove ( ref );
}
2026-10-12 22:18:32 +02:00
/**
* Removes a handler registered with {@link #addHandler(ResourceLocation, Runnable)}.
*
* @return true if handler was matched and removed.
*/
public static synchronized boolean removeHandler ( ResourceLocation registry , Runnable ref )
{
var handlers = namedHandlers . get ( registry );
return handlers != null && handlers . remove ( ref );
}
/**
* Registers a check that is run once, before the handlers of the next holder pass.
* Used to report holders that point at a registry that does not exist.
*/
public static synchronized void addValidator ( Runnable ref )
{
validators . add ( ref );
}
2019-01-09 20:57:01 -08:00
//==============================================================
// Everything below is internal, do not use.
//==============================================================
2018-09-06 04:39:01 -07:00
private static final Logger LOGGER = LogManager . getLogger ();
2019-01-16 12:28:13 -08:00
private static final Set < Consumer < Predicate < ResourceLocation >>> objectHolders = new HashSet <> ();
2026-10-12 22:18:32 +02:00
// Order is not guaranteed, the old implementation kept every handler in one HashSet.
private static final Map < ResourceLocation , Set < Runnable >> namedHandlers = new HashMap <> ();
private static final List < Runnable > validators = new ArrayList <> ();
2019-01-09 20:57:01 -08:00
private static final Type OBJECT_HOLDER = Type . getType ( ObjectHolder . class );
private static final Type MOD = Type . getType ( Mod . class );
2022-06-07 15:31:26 -05:00
// Hardcoded list of vanilla classes that should have object holders for each field of the given registry type.
// IMPORTANT: Updates to this collection must be reflected in ObjectHolderDefinalize. Duplicated cuz classloaders, yay!
// Classnames are validated below.
private static final List < VanillaObjectHolderData > VANILLA_OBJECT_HOLDERS = List . of (
new VanillaObjectHolderData ( "net.minecraft.world.level.block.Blocks" , "block" , "net.minecraft.world.level.block.Block" ),
new VanillaObjectHolderData ( "net.minecraft.world.item.Items" , "item" , "net.minecraft.world.item.Item" ),
new VanillaObjectHolderData ( "net.minecraft.world.item.enchantment.Enchantments" , "enchantment" , "net.minecraft.world.item.enchantment.Enchantment" ),
new VanillaObjectHolderData ( "net.minecraft.world.effect.MobEffects" , "mob_effect" , "net.minecraft.world.effect.MobEffect" ),
new VanillaObjectHolderData ( "net.minecraft.core.particles.ParticleTypes" , "particle_type" , "net.minecraft.core.particles.ParticleType" ),
new VanillaObjectHolderData ( "net.minecraft.sounds.SoundEvents" , "sound_event" , "net.minecraft.sounds.SoundEvent" )
);
2019-01-09 20:57:01 -08:00
public static void findObjectHolders ()
2014-05-24 22:32:24 -04:00
{
2023-07-17 17:26:28 -07:00
LOGGER . debug ( ForgeRegistry . REGISTRIES , "Processing ObjectHolder annotations" );
2026-10-13 09:21:04 -07:00
final HashMap < Type , String > classModIds = new HashMap <> ();
final List < ModFileScanData . AnnotationData > annotations = new ArrayList <> ();
2018-09-06 04:39:01 -07:00
2026-10-13 09:21:04 -07:00
for ( var data : ModList . get (). getAllScanData ()) {
for ( var annotation : data . getAnnotations ()) {
if ( OBJECT_HOLDER . equals ( annotation . annotationType ())) {
annotations . add ( annotation );
}
// Gather all @Mod classes so that @ObjectHolder's in those classes don't need to specify the mod id; modder convenience
else if ( MOD . equals ( annotation . annotationType ())) {
classModIds . put ( annotation . clazz (), ( String ) annotation . annotationData (). get ( "value" ));
}
}
}
2024-12-23 21:56:46 -05:00
2026-10-13 09:21:04 -07:00
// this is never actually true as we have an annotation in ConditionalRecipe, but also we want to always run the vanilla holders
/*
if (annotations.isEmpty()) {
return; // No object holders found, skip the rest of the processing,
}
*/
2019-01-09 20:57:01 -08:00
2026-10-13 09:21:04 -07:00
Map < Type , Class <?>> classCache = Maps . newHashMap ();
2022-06-07 15:31:26 -05:00
// Validate all the vanilla class-level object holders then scan those first
2026-10-13 09:21:04 -07:00
for ( var data : VANILLA_OBJECT_HOLDERS ) {
try {
2022-06-07 15:31:26 -05:00
Class <?> holderClass = Class . forName ( data . holderClass (), true , ObjectHolderRegistry . class . getClassLoader ());
Class <?> registryClass = Class . forName ( data . registryType (), true , ObjectHolderRegistry . class . getClassLoader ());
Type holderType = Type . getType ( holderClass );
classCache . put ( holderType , holderClass );
scanTarget ( classModIds , classCache , holderType , null , registryClass , data . registryName (), "minecraft" , true , true );
2026-10-13 09:21:04 -07:00
} catch ( ClassNotFoundException e ) {
2022-06-07 15:31:26 -05:00
throw new RuntimeException ( "Vanilla class not found, should not be possible" , e );
}
2026-10-13 09:21:04 -07:00
}
2018-09-06 04:39:01 -07:00
2022-06-07 15:31:26 -05:00
// Scan actual fields annotated with @ObjectHolder second
2026-10-13 09:21:04 -07:00
for ( var data : annotations ) {
if ( data . targetType () == ElementType . FIELD ) {
var registry = ( String ) data . annotationData (). get ( "registryName" );
var name = ( String ) data . annotationData (). get ( "value" );
scanTarget ( classModIds , classCache , data . clazz (), data . memberName (), null , registry , name , false , false );
}
// This is where we WOULD scan for class targets, but Shrimp broke that in 1.19 and so I don't want to re-add a feature that apparently nobody used.
}
int count = objectHolders . size ();
for ( var set : namedHandlers . values ())
count += set . size ();
2019-01-09 20:57:01 -08:00
2026-10-13 09:21:04 -07:00
LOGGER . debug ( ForgeRegistry . REGISTRIES , "Found {} ObjectHolder annotations" , count );
2014-05-24 22:32:24 -04:00
}
2019-01-09 20:57:01 -08:00
2022-06-07 15:31:26 -05:00
private static void scanTarget ( Map < Type , String > classModIds , Map < Type , Class <?>> classCache , Type type ,
@Nullable String annotationTarget , @Nullable Class <?> registryClass , String registryName ,
String value , boolean isClass , boolean extractFromValue )
2014-05-24 22:32:24 -04:00
{
Class <?> clazz ;
2019-01-09 20:57:01 -08:00
if ( classCache . containsKey ( type ))
2014-05-24 22:32:24 -04:00
{
2019-01-09 20:57:01 -08:00
clazz = classCache . get ( type );
2014-05-24 22:32:24 -04:00
}
else
{
try
{
2019-01-09 20:57:01 -08:00
clazz = Class . forName ( type . getClassName (), extractFromValue , ObjectHolderRegistry . class . getClassLoader ());
classCache . put ( type , clazz );
2014-05-24 22:32:24 -04:00
}
2017-07-13 05:50:28 +01:00
catch ( ClassNotFoundException ex )
2014-05-24 22:32:24 -04:00
{
// unpossible?
2017-06-19 15:02:18 -07:00
throw new RuntimeException ( ex );
2014-05-24 22:32:24 -04:00
}
}
2026-10-12 22:18:32 +02:00
ResourceLocation regName = ResourceLocation . parse ( registryName );
2014-05-24 22:32:24 -04:00
if ( isClass )
{
2026-10-12 22:18:32 +02:00
scanClassForFields ( classModIds , type , regName , registryClass , value , clazz , extractFromValue );
2014-05-24 22:32:24 -04:00
}
else
{
if ( value . indexOf ( ':' ) == - 1 )
{
2019-01-09 20:57:01 -08:00
String prefix = classModIds . get ( type );
2014-05-24 22:32:24 -04:00
if ( prefix == null )
{
2023-07-17 17:26:28 -07:00
LOGGER . warn ( ForgeRegistry . REGISTRIES , "Found an unqualified ObjectHolder annotation ({}) without a modid context at {}.{}, ignoring" , value , type , annotationTarget );
2014-05-24 22:32:24 -04:00
throw new IllegalStateException ( "Unqualified reference to ObjectHolder" );
}
2019-01-09 20:57:01 -08:00
value = prefix + ':' + value ;
2014-05-24 22:32:24 -04:00
}
try
{
2017-06-20 18:39:55 -07:00
Field f = clazz . getDeclaredField ( annotationTarget );
2026-10-12 22:18:32 +02:00
ObjectHolderRef ref = ObjectHolderRef . create ( regName , f , value , extractFromValue );
2022-06-07 15:31:26 -05:00
if ( ref != null )
2026-10-12 22:18:32 +02:00
addHandler ( regName , ref );
2014-05-24 22:32:24 -04:00
}
2017-07-13 05:50:28 +01:00
catch ( NoSuchFieldException ex )
2014-05-24 22:32:24 -04:00
{
// unpossible?
2017-06-19 15:02:18 -07:00
throw new RuntimeException ( ex );
2014-05-24 22:32:24 -04:00
}
}
}
2022-06-07 15:31:26 -05:00
private static void scanClassForFields ( Map < Type , String > classModIds , Type targetClass ,
ResourceLocation registryName , Class <?> registryClass , String value , Class <?> clazz , boolean extractFromExistingValues )
2014-05-24 22:32:24 -04:00
{
2019-01-09 20:57:01 -08:00
classModIds . put ( targetClass , value );
2018-09-06 04:39:01 -07:00
final int flags = Opcodes . ACC_PUBLIC | Opcodes . ACC_STATIC | Opcodes . ACC_SYNTHETIC ;
2014-05-24 22:32:24 -04:00
for ( Field f : clazz . getFields ())
{
2022-06-07 15:31:26 -05:00
if ((( f . getModifiers () & flags ) != flags ) || f . isAnnotationPresent ( ObjectHolder . class ) || ! registryClass . isAssignableFrom ( f . getType ()))
2014-05-24 22:32:24 -04:00
continue ;
2022-06-07 15:31:26 -05:00
ObjectHolderRef ref = ObjectHolderRef . create ( registryName , f , value + ':' + f . getName (). toLowerCase ( Locale . ENGLISH ), extractFromExistingValues );
if ( ref != null )
2026-10-12 22:18:32 +02:00
addHandler ( registryName , ref );
2014-05-26 10:58:13 -04:00
}
2014-05-24 22:32:24 -04:00
}
2022-06-07 15:31:26 -05:00
private static ResourceLocation getRegistryName ( Map < Type , ResourceLocation > classRegistryNames , @Nullable String registryName ,
Type targetClass , Object declaration )
{
if ( registryName != null )
return new ResourceLocation ( registryName );
if ( classRegistryNames . containsKey ( targetClass ))
return classRegistryNames . get ( targetClass );
throw new IllegalStateException ( "No registry name was declared for " + declaration );
}
2026-10-12 22:18:32 +02:00
private static final Predicate < ResourceLocation > ALWAYS_TRUE = name -> true ;
public static void applyObjectHolders () {
try {
2023-07-17 17:26:28 -07:00
LOGGER . debug ( ForgeRegistry . REGISTRIES , "Applying holder lookups" );
2026-10-12 22:18:32 +02:00
var exceptions = new ArrayList < Exception > ();
validate ( exceptions );
// Call named registries
for ( var handlers : namedHandlers . values ())
apply ( handlers , exceptions );
// Call legacy for backward compatibility
2026-10-13 09:21:04 -07:00
if ( ! objectHolders . isEmpty ())
apply ( ALWAYS_TRUE , objectHolders , exceptions );
2026-10-12 22:18:32 +02:00
throwIfNeeded ( exceptions );
2023-07-17 17:26:28 -07:00
LOGGER . debug ( ForgeRegistry . REGISTRIES , "Holder lookups applied" );
2026-10-12 22:18:32 +02:00
} catch ( RuntimeException e ) {
2022-06-07 15:31:26 -05:00
// It is more important that the calling contexts continue without exception to prevent further cascading errors
LOGGER . error ( "" , e );
}
2014-05-24 22:32:24 -04:00
}
2026-10-12 22:18:32 +02:00
/**
* @deprecated use {@link #applyObjectHolders(ResourceLocation)}
*/
2019-01-16 12:28:13 -08:00
public static void applyObjectHolders ( Predicate < ResourceLocation > filter )
{
2026-10-12 22:18:32 +02:00
LOGGER . debug ( ForgeRegistry . REGISTRIES , "Apply filtered holder lookups" );
var exceptions = new ArrayList < Exception > ();
validate ( exceptions );
// call new registries if needed
for ( var entry : namedHandlers . entrySet ()) {
if ( filter . test ( entry . getKey ())) {
apply ( entry . getValue (), exceptions );
}
}
// Call legacy
2026-10-13 09:21:04 -07:00
if ( ! objectHolders . isEmpty ())
apply ( filter , objectHolders , exceptions );
2026-10-12 22:18:32 +02:00
throwIfNeeded ( exceptions );
LOGGER . debug ( ForgeRegistry . REGISTRIES , "Holder lookups applied" );
}
/** Runs the handlers of one registry. */
public static void applyObjectHolders ( ResourceLocation registry ) {
var exceptions = new ArrayList < Exception > ();
validate ( exceptions );
2026-10-13 09:21:04 -07:00
2026-10-12 22:18:32 +02:00
var handlers = namedHandlers . get ( registry );
if ( handlers != null )
apply ( handlers , exceptions );
2026-10-13 09:21:04 -07:00
if ( ! objectHolders . isEmpty ())
apply ( registry :: equals , objectHolders , exceptions );
2026-10-12 22:18:32 +02:00
throwIfNeeded ( exceptions );
}
// Each validator runs once, at the first holder pass after it was registered.
private static void validate ( List < Exception > exceptions ) {
List < Runnable > pending ;
synchronized ( ObjectHolderRegistry . class ) {
if ( validators . isEmpty ())
return ;
pending = new ArrayList <> ( validators );
validators . clear ();
}
for ( var validator : pending ) {
try {
validator . run ();
} catch ( Exception e ) {
exceptions . add ( e );
2022-04-01 15:49:46 -05:00
}
2026-10-12 22:18:32 +02:00
}
}
2022-04-01 15:49:46 -05:00
2026-10-12 22:18:32 +02:00
private static void apply ( Set < Runnable > handlers , List < Exception > exceptions ) {
for ( var handler : handlers ) {
try {
handler . run ();
} catch ( Exception e ) {
exceptions . add ( e );
}
2022-04-01 15:49:46 -05:00
}
2019-01-16 12:28:13 -08:00
}
2026-10-13 09:21:04 -07:00
private static void apply ( Predicate < ResourceLocation > filter , Set < Consumer < Predicate < ResourceLocation >>> handlers , List < Exception > exceptions ) {
for ( var handler : handlers ) {
try {
handler . accept ( filter );
} catch ( Exception e ) {
exceptions . add ( e );
}
}
}
2026-10-12 22:18:32 +02:00
private static void throwIfNeeded ( List < Exception > exceptions ) {
if ( exceptions . isEmpty ())
return ;
var aggregate = new RuntimeException ( "Failed to apply some object holders, see suppressed exceptions for details" );
for ( var e : exceptions )
aggregate . addSuppressed ( e );
throw aggregate ;
}
2022-06-07 15:31:26 -05:00
private record VanillaObjectHolderData ( String holderClass , String registryName , String registryType ) {}
2014-05-24 22:32:24 -04:00
}