[1.13] Make Caps, TESR, Entity renderers, and keybinds thread safe to call during parallel init (#5359)

This commit is contained in:
Vincent Lee 2019-01-25 15:14:49 -06:00 committed by LexManos
parent 2c8ab76240
commit fa939a890c
4 changed files with 49 additions and 40 deletions

View file

@ -28,17 +28,23 @@ import java.lang.annotation.*;
* of 'Capability'
*
* Example:
* @CapabilityInject(IExampleCapability.class)
* <pre>
* {@literal @}CapabilityInject(IExampleCapability.class)
* private static final Capability<IExampleCapability> TEST_CAP = null;
* </pre>
*
* When placed on a METHOD, the method will be invoked once the
* capability is registered. This allows you to have a 'enable features'
* callback. It MUST have one parameter of type 'Capability;
*
* Example:
* @CapabilityInject(IExampleCapability.class)
* <pre>
* {@literal @}CapabilityInject(IExampleCapability.class)
* private static void capRegistered(Capability<IExampleCapability> cap) {}
* </pre>
*
* <b>Warning</b>: Capability injections are run in the thread that the capablity is registered.
* Due to parallel mod loading, this can potentially be off of the main thread.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})

View file

@ -27,6 +27,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
@ -50,6 +51,7 @@ public enum CapabilityManager
* Registers a capability to be consumed by others.
* APIs who define the capability should call this.
* To retrieve the Capability instance, use the @CapabilityInject annotation.
* This method is safe to call during parallel mod loading.
*
* @param type The Interface to be registered
* @param storage A default implementation of the storage handler.
@ -61,20 +63,25 @@ public enum CapabilityManager
Objects.requireNonNull(storage,"Attempted to register a capability with no storage implementation");
Objects.requireNonNull(factory,"Attempted to register a capability with no default implementation factory");
String realName = type.getName().intern();
Capability<T> cap;
synchronized (providers)
{
if (providers.containsKey(realName)) {
LOGGER.error(CAPABILITIES, "Cannot register capability implementation multiple times : {}", realName);
throw new IllegalArgumentException("Cannot register a capability implementation multiple times : "+ realName);
}
Capability<T> cap = new Capability<>(realName, storage, factory);
cap = new Capability<>(realName, storage, factory);
providers.put(realName, cap);
}
callbacks.getOrDefault(realName, Collections.emptyList()).forEach(func -> func.apply(cap));
}
// INTERNAL
private IdentityHashMap<String, Capability<?>> providers = new IdentityHashMap<>();
private IdentityHashMap<String, List<Function<Capability<?>, Object>>> callbacks = new IdentityHashMap<>();
private final IdentityHashMap<String, Capability<?>> providers = new IdentityHashMap<>();
private volatile IdentityHashMap<String, List<Function<Capability<?>, Object>>> callbacks;
public void injectCapabilities(List<ModFileScanData> data)
{
final List<ModFileScanData.AnnotationData> capabilities = data.stream()
@ -82,10 +89,12 @@ public enum CapabilityManager
.flatMap(Collection::stream)
.filter(a -> CAP_INJECT.equals(a.getAnnotationType()))
.collect(Collectors.toList());
capabilities.forEach(this::attachCapabilityToMethod);
final IdentityHashMap<String, List<Function<Capability<?>, Object>>> m = new IdentityHashMap<>();
capabilities.forEach(entry -> attachCapabilityToMethod(m, entry));
callbacks = m;
}
private void attachCapabilityToMethod(ModFileScanData.AnnotationData entry)
private static void attachCapabilityToMethod(Map<String, List<Function<Capability<?>, Object>>> cbs, ModFileScanData.AnnotationData entry)
{
final String targetClass = entry.getClassType().getClassName();
final String targetName = entry.getMemberName();
@ -97,7 +106,7 @@ public enum CapabilityManager
}
final String capabilityName = type.getInternalName().replace('/', '.').intern();
List<Function<Capability<?>, Object>> list = callbacks.computeIfAbsent(capabilityName, k -> new ArrayList<>());
List<Function<Capability<?>, Object>> list = cbs.computeIfAbsent(capabilityName, k -> new ArrayList<>());
if (entry.getMemberName().indexOf('(') > 0)
{

View file

@ -19,7 +19,6 @@
package net.minecraftforge.fml.client.registry;
import com.google.common.collect.Maps;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
@ -29,35 +28,31 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.fml.common.registry.GameRegistry;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ClientRegistry
{
private static Map<Class<? extends Entity>, ResourceLocation> entityShaderMap = Maps.newHashMap();
private static Map<Class<? extends Entity>, ResourceLocation> entityShaderMap = new ConcurrentHashMap<>();
/**
*
* Utility method for registering a tile entity and it's renderer at once - generally you should register them separately
*
* @param tileEntityClass
* @param id
* @param specialRenderer
* / TODO GameRegistry
public static <T extends TileEntity> void registerTileEntity(Class<T> tileEntityClass, String id, TileEntityRenderer<? super T> specialRenderer)
{
GameRegistry.registerTileEntity(tileEntityClass, id);
bindTileEntitySpecialRenderer(tileEntityClass, specialRenderer);
}
*/
public static <T extends TileEntity> void bindTileEntitySpecialRenderer(Class<T> tileEntityClass, TileEntityRenderer<? super T> specialRenderer)
* Registers a Tile Entity renderer.
* Call this during {@link net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent}.
* This method is safe to call during parallel mod loading.
*/
public static synchronized <T extends TileEntity> void bindTileEntitySpecialRenderer(Class<T> tileEntityClass, TileEntityRenderer<? super T> specialRenderer)
{
TileEntityRendererDispatcher.instance.renderers.put(tileEntityClass, specialRenderer);
specialRenderer.setRendererDispatcher(TileEntityRendererDispatcher.instance);
}
public static void registerKeyBinding(KeyBinding key)
/**
* Registers a KeyBinding.
* Call this during {@link net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent}.
* This method is safe to call during parallel mod loading.
*/
public static synchronized void registerKeyBinding(KeyBinding key)
{
Minecraft.getInstance().gameSettings.keyBindings = ArrayUtils.add(Minecraft.getInstance().gameSettings.keyBindings, key);
}
@ -65,9 +60,8 @@ public class ClientRegistry
/**
* Register a shader for an entity. This shader gets activated when a spectator begins spectating an entity.
* Vanilla examples of this are the green effect for creepers and the invert effect for endermen.
*
* @param entityClass
* @param shader
* Call this during {@link net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent}.
* This method is safe to call during parallel mod loading.
*/
public static void registerEntityShader(Class<? extends Entity> entityClass, ResourceLocation shader)
{

View file

@ -20,23 +20,23 @@
package net.minecraftforge.fml.client.registry;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
import com.google.common.collect.Maps;
public class RenderingRegistry
{
private static final RenderingRegistry INSTANCE = new RenderingRegistry();
private Map<Class<? extends Entity>, IRenderFactory<? extends Entity>> entityRenderers = Maps.newHashMap();
private final Map<Class<? extends Entity>, IRenderFactory<? extends Entity>> entityRenderers = new ConcurrentHashMap<>();
/**
* Register an entity rendering handler. This will, after mod initialization, be inserted into the main
* render map for entities.
* Call this during Preinitialization phase.
* Call this during {@link net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent}.
* This method is safe to call during parallel mod loading.
*/
public static <T extends Entity> void registerEntityRenderingHandler(Class<T> entityClass, IRenderFactory<? super T> renderFactory)
{