Add in the objectholder, autopopulated by FML based on simple rules. Allows

for reference driven substitution of mod blocks and items based on their
server running state.
This commit is contained in:
Christian 2014-05-24 22:32:24 -04:00
parent 2c5fcc6129
commit 05cde0c92f
5 changed files with 255 additions and 0 deletions

View file

@ -62,6 +62,7 @@ import cpw.mods.fml.common.functions.ArtifactVersionNameFunction;
import cpw.mods.fml.common.functions.ModIdFunction;
import cpw.mods.fml.common.registry.GameData;
import cpw.mods.fml.common.registry.GameRegistry.Type;
import cpw.mods.fml.common.registry.ObjectHolderRegistry;
import cpw.mods.fml.common.toposort.ModSorter;
import cpw.mods.fml.common.toposort.ModSortingException;
import cpw.mods.fml.common.toposort.ModSortingException.SortingExceptionData;
@ -502,12 +503,14 @@ public class Loader
public void preinitializeMods()
{
ObjectHolderRegistry.INSTANCE.findObjectHolders(discoverer.getASMTable());
if (!modController.isInState(LoaderState.PREINITIALIZATION))
{
FMLLog.warning("There were errors previously. Not beginning mod initialization phase");
return;
}
modController.distributeStateMessage(LoaderState.PREINITIALIZATION, discoverer.getASMTable(), canonicalConfigDir);
ObjectHolderRegistry.INSTANCE.applyObjectHolders();
modController.transition(LoaderState.INITIALIZATION, false);
}

View file

@ -530,6 +530,8 @@ public class GameData {
getMain().iBlockRegistry.dump();
getMain().iItemRegistry.dump();
Loader.instance().fireRemapEvent(remaps);
// The id map changed, ensure we apply object holders
ObjectHolderRegistry.INSTANCE.applyObjectHolders();
return ImmutableList.of();
}
@ -664,6 +666,8 @@ public class GameData {
getMain().set(frozen);
}
// the id mapping has reverted, ensure we sync up the object holders
ObjectHolderRegistry.INSTANCE.applyObjectHolders();
}
protected static boolean isFrozen(FMLControlledNamespacedRegistry<?> registry)

View file

@ -12,6 +12,10 @@
package cpw.mods.fml.common.registry;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
@ -424,4 +428,24 @@ public class GameRegistry
return GameData.getUniqueName(item);
}
/**
* This will cause runtime injection of public static final fields to occur at various points
* where mod blocks and items <em>could</em> be subject to change. This allows for dynamic
* substitution to occur.
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface ObjectHolder {
/**
* If used on a class, this represents a modid only.
* If used on a field, it represents a name, which can be abbreviated or complete.
* Abbreviated names derive their modid from an enclosing ObjectHolder at the class level.
*
* @return either a modid or a name based on the rules above
*/
String value();
}
}

View file

@ -0,0 +1,101 @@
package cpw.mods.fml.common.registry;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.apache.logging.log4j.Level;
import com.google.common.base.Throwables;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.registry.GameRegistry.ObjectHolder;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
/**
* Internal class used in tracking {@link ObjectHolder} references
*
* @author cpw
*
*/
class ObjectHolderRef {
private Field field;
private String injectedObject;
private boolean isBlock;
private boolean isItem;
ObjectHolderRef(Field field, String injectedObject, boolean extractFromExistingValues)
{
this.field = field;
this.isBlock = Block.class.isAssignableFrom(field.getType());
this.isItem = Item.class.isAssignableFrom(field.getType());
if (extractFromExistingValues)
{
try
{
Object existing = field.get(null);
this.injectedObject = isBlock ? GameData.getBlockRegistry().getNameForObject(existing) :
isItem ? GameData.getItemRegistry().getNameForObject(existing) : null;
} catch (Exception e)
{
throw Throwables.propagate(e);
}
}
else
{
this.injectedObject = injectedObject;
}
if (this.injectedObject == null || !isValid())
{
throw new IllegalStateException("The ObjectHolder annotation cannot apply to a field that is not an Item or Block");
}
makeWritable(field);
}
private static Field modifiersField;
private static Object reflectionFactory;
private static Method newFieldAccessor;
private static Method fieldAccessorSet;
private static void makeWritable(Field f)
{
try
{
if (modifiersField == null)
{
Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory");
reflectionFactory = getReflectionFactory.invoke(null);
newFieldAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class);
fieldAccessorSet = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class);
modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
}
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
} catch (Exception e)
{
throw Throwables.propagate(e);
}
}
public boolean isValid()
{
return isBlock || isItem;
}
public void apply()
{
Object thing;
if (isBlock)
thing = GameData.getBlockRegistry().getObject(injectedObject);
else
thing = GameData.getItemRegistry().getObject(injectedObject);
try
{
Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false);
fieldAccessorSet.invoke(fieldAccessor, null, thing);
}
catch (Exception e)
{
FMLLog.log(Level.WARN, e, "Unable to set %s with value %s (%s)", this.field, thing, this.injectedObject);
}
}
}

View file

@ -0,0 +1,123 @@
package cpw.mods.fml.common.registry;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.discovery.ASMDataTable;
import cpw.mods.fml.common.discovery.ASMDataTable.ASMData;
import cpw.mods.fml.common.registry.GameRegistry.ObjectHolder;
/**
* Internal registry for tracking {@link ObjectHolder} references
* @author cpw
*
*/
public enum ObjectHolderRegistry {
INSTANCE;
private List<ObjectHolderRef> objectHolders = Lists.newArrayList();
public void findObjectHolders(ASMDataTable table)
{
FMLLog.info("Processing ObjectHolder annotations");
Set<ASMData> allObjectHolders = table.getAll(GameRegistry.ObjectHolder.class.getName());
Map<String, String> classModIds = Maps.newHashMap();
Map<String, Class<?>> classCache = Maps.newHashMap();
for (ASMData data : allObjectHolders)
{
String className = data.getClassName();
String annotationTarget = data.getObjectName();
String value = (String) data.getAnnotationInfo().get("value");
boolean isClass = className.equals(annotationTarget);
scanTarget(classModIds, classCache, className, annotationTarget, value, isClass, false);
}
scanTarget(classModIds, classCache, "net.minecraft.init.Blocks", null, "minecraft", true, true);
scanTarget(classModIds, classCache, "net.minecraft.init.Items", null, "minecraft", true, true);
FMLLog.info("Found %d ObjectHolder annotations", objectHolders.size());
}
private void scanTarget(Map<String, String> classModIds, Map<String, Class<?>> classCache, String className, String annotationTarget, String value, boolean isClass, boolean extractFromValue)
{
Class<?> clazz;
if (classCache.containsKey(className))
{
clazz = classCache.get(className);
}
else
{
try
{
clazz = Class.forName(className, true, getClass().getClassLoader());
classCache.put(className, clazz);
}
catch (Exception ex)
{
// unpossible?
throw Throwables.propagate(ex);
}
}
if (isClass)
{
scanClassForFields(classModIds, className, value, clazz, extractFromValue);
}
else
{
if (value.indexOf(':') == -1)
{
String prefix = classModIds.get(className);
if (prefix == null)
{
FMLLog.warning("Found an unqualified ObjectHolder annotation (%s) without a modid context at %s.%s, ignoring", value, className, annotationTarget);
throw new IllegalStateException("Unqualified reference to ObjectHolder");
}
value = prefix + ":" + value;
}
try
{
Field f = clazz.getField(annotationTarget);
addHolderReference(new ObjectHolderRef(f, value, extractFromValue));
}
catch (Exception ex)
{
// unpossible?
throw Throwables.propagate(ex);
}
}
}
private void scanClassForFields(Map<String, String> classModIds, String className, String value, Class<?> clazz, boolean extractFromExistingValues)
{
classModIds.put(className, value);
for (Field f : clazz.getFields())
{
int mods = f.getModifiers();
boolean isMatch = Modifier.isPublic(mods) && Modifier.isStatic(mods) && Modifier.isFinal(mods);
if (!isMatch || f.isAnnotationPresent(ObjectHolder.class))
{
continue;
}
addHolderReference(new ObjectHolderRef(f, value + ":"+ f.getName(), extractFromExistingValues));
}
}
private void addHolderReference(ObjectHolderRef ref)
{
objectHolders.add(ref);
}
public void applyObjectHolders()
{
FMLLog.info("Applying holder lookups");
for (ObjectHolderRef ohr : objectHolders)
{
ohr.apply();
}
FMLLog.info("Holder lookups applied");
}
}