Add callback to allow registration of feature renderers. (#873)

* Add callback to allow registration of feature renderers.

* Laymans terms for those who don't map yarn for a living.

* Be a little less generic

* Dispatch events in a better spot, play with generics again, move to renderer-registries.

Also move this to render registries

* Handle players as well

* Tweak generics

* Hide acceptor impl

* Add actual testmod, fix where player events are fired, Simplify to returning a list.

* the old style worked fine

* registerRenderers

* Rename the event interface

* Javadoc fixes
This commit is contained in:
i509VCB 2020-08-07 13:49:39 -05:00 committed by GitHub
parent 02fb8fda0e
commit e2862de602
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 413 additions and 5 deletions

View file

@ -1,6 +1,8 @@
archivesBaseName = "fabric-renderer-registries-v1"
version = getSubprojectVersion(project, "2.0.1")
version = getSubprojectVersion(project, "2.1.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
testmodCompile project(path: ':fabric-lifecycle-events-v1', configuration: 'dev')
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.client.rendereregistry.v1;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.feature.Deadmau5FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Called when {@link FeatureRenderer feature renderers} for a {@link LivingEntityRenderer living entity renderer} are registered.
*
* <p>Feature renderers are typically used for rendering additional objects on an entity, such as armor, an elytra or {@link Deadmau5FeatureRenderer Deadmau5's ears}.
* This callback lets developers add additional feature renderers for use in entity rendering.
* Listeners should filter out the specific entity renderer they want to hook into, usually through {@code instanceof} checks or filtering by entity type.
* Once listeners find a suitable entity renderer, they should register their feature renderer via the registration helper.
*
* <p>For example, to register a feature renderer for a player model, the example below may used:
* <blockquote><pre>
* LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper) -> {
* if (entityRenderer instanceof PlayerEntityModel) {
* registrationHelper.register(new MyFeatureRenderer((PlayerEntityModel) entityRenderer));
* }
* });
* </pre></blockquote>
*/
@FunctionalInterface
@Environment(EnvType.CLIENT)
public interface LivingEntityFeatureRendererRegistrationCallback {
Event<LivingEntityFeatureRendererRegistrationCallback> EVENT = EventFactory.createArrayBacked(LivingEntityFeatureRendererRegistrationCallback.class, callbacks -> (entityType, entityRenderer, registrationHelper) -> {
for (LivingEntityFeatureRendererRegistrationCallback callback : callbacks) {
callback.registerRenderers(entityType, entityRenderer, registrationHelper);
}
});
/**
* Called when feature renderers may be registered.
*
* @param entityType the entity type of the renderer
* @param entityRenderer the entity renderer
*/
void registerRenderers(EntityType<? extends LivingEntity> entityType, LivingEntityRenderer<?, ?> entityRenderer, RegistrationHelper registrationHelper);
/**
* A delegate object used to help register feature renderers for an entity renderer.
*
* <p>This is not meant for implementation by users of the API.
*/
interface RegistrationHelper {
/**
* Adds a feature renderer to the entity renderer.
*
* @param featureRenderer the feature renderer
* @param <T> the type of entity
*/
<T extends LivingEntity> void register(FeatureRenderer<T, ? extends EntityModel<T>> featureRenderer);
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.client.renderer.registry;
import java.util.Objects;
import java.util.function.Function;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.entity.LivingEntity;
import net.fabricmc.fabric.api.client.rendereregistry.v1.LivingEntityFeatureRendererRegistrationCallback;
public final class RegistrationHelperImpl implements LivingEntityFeatureRendererRegistrationCallback.RegistrationHelper {
private final Function<FeatureRenderer<?, ?>, Boolean> delegate;
public RegistrationHelperImpl(Function<FeatureRenderer<?, ?>, Boolean> delegate) {
this.delegate = delegate;
}
@Override
public <T extends LivingEntity> void register(FeatureRenderer<T, ? extends EntityModel<T>> featureRenderer) {
Objects.requireNonNull(featureRenderer, "Feature renderer cannot be null");
this.delegate.apply(featureRenderer);
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.client.renderer.registry;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.entity.LivingEntity;
@Mixin(LivingEntityRenderer.class)
public interface LivingEntityRendererAccessor<T extends LivingEntity, M extends EntityModel<T>> {
@Invoker("addFeature")
boolean callAddFeature(FeatureRenderer<T, M> featureRenderer);
}

View file

@ -18,6 +18,7 @@ package net.fabricmc.fabric.mixin.client.renderer.registry;
import java.util.Map;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@ -26,20 +27,51 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.render.entity.EntityRenderDispatcher;
import net.minecraft.client.render.entity.EntityRenderer;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.PlayerEntityRenderer;
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.resource.ReloadableResourceManager;
import net.fabricmc.fabric.api.client.rendereregistry.v1.EntityRendererRegistry;
import net.fabricmc.fabric.api.client.rendereregistry.v1.LivingEntityFeatureRendererRegistrationCallback;
import net.fabricmc.fabric.impl.client.renderer.registry.RegistrationHelperImpl;
@Mixin(EntityRenderDispatcher.class)
public class MixinEntityRenderDispatcher {
public abstract class MixinEntityRenderDispatcher {
@Shadow
Map<EntityType<?>, EntityRenderer<?>> renderers;
@Final
private Map<EntityType<?>, EntityRenderer<?>> renderers;
@Inject(method = "registerRenderers", at = @At(value = "RETURN"), require = 1)
public void on_method_23167(ItemRenderer itemRenderer, ReloadableResourceManager manager, CallbackInfo info) {
@Shadow
@Final
private Map<String, PlayerEntityRenderer> modelRenderers;
@SuppressWarnings({"unchecked", "rawtypes"})
@Inject(method = "registerRenderers", at = @At(value = "TAIL"))
public void onRegisterRenderers(ItemRenderer itemRenderer, ReloadableResourceManager manager, CallbackInfo info) {
final EntityRenderDispatcher me = (EntityRenderDispatcher) (Object) this;
EntityRendererRegistry.INSTANCE.initialize(me, me.textureManager, manager, itemRenderer, renderers);
// Dispatch events to register feature renderers.
for (Map.Entry<EntityType<?>, EntityRenderer<?>> entry : this.renderers.entrySet()) {
if (entry.getValue() instanceof LivingEntityRenderer) { // Must be living for features
LivingEntityRendererAccessor accessor = (LivingEntityRendererAccessor) entry.getValue();
LivingEntityFeatureRendererRegistrationCallback.EVENT.invoker().registerRenderers((EntityType<? extends LivingEntity>) entry.getKey(), (LivingEntityRenderer) entry.getValue(), new RegistrationHelperImpl(accessor::callAddFeature));
}
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Inject(method = "<init>", at = @At("TAIL"))
private void afterRegisterPlayerModels(CallbackInfo ci) {
// Players are a fun case, we need to do these separately and per model type
for (Map.Entry<String, PlayerEntityRenderer> entry : this.modelRenderers.entrySet()) {
LivingEntityRendererAccessor accessor = (LivingEntityRendererAccessor) entry.getValue();
LivingEntityFeatureRendererRegistrationCallback.EVENT.invoker().registerRenderers(EntityType.PLAYER, entry.getValue(), new RegistrationHelperImpl(accessor::callAddFeature));
}
}
}

View file

@ -3,6 +3,7 @@
"package": "net.fabricmc.fabric.mixin.client.renderer.registry",
"compatibilityLevel": "JAVA_8",
"client": [
"LivingEntityRendererAccessor",
"MixinBlockEntityRenderDispatcher",
"MixinEntityRenderDispatcher"
],

View file

@ -0,0 +1,121 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.renderer.registry;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.ArmorStandEntityRenderer;
import net.minecraft.client.render.entity.BipedEntityRenderer;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.PlayerEntityRenderer;
import net.minecraft.client.render.entity.feature.ElytraFeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import net.minecraft.client.render.entity.feature.HeldItemFeatureRenderer;
import net.minecraft.client.render.entity.model.ArmorStandArmorEntityModel;
import net.minecraft.client.render.entity.model.PlayerEntityModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.decoration.ArmorStandEntity;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.rendereregistry.v1.LivingEntityFeatureRendererRegistrationCallback;
/**
* This test exists solely for testing generics.
* As such it is not in the mod json
*/
public class FeatureRendererGenericTests implements ClientModInitializer {
@Override
public void onInitializeClient() {
// These aren't tests in the normal sense. These exist to test that generics are sane.
LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper) -> {
if (entityRenderer instanceof PlayerEntityRenderer) {
registrationHelper.register(new TestPlayerFeature((PlayerEntityRenderer) entityRenderer));
// This is T extends AbstractClientPlayerEntity
registrationHelper.register(new GenericTestPlayerFeature<>((PlayerEntityRenderer) entityRenderer));
}
if (entityRenderer instanceof ArmorStandEntityRenderer) {
registrationHelper.register(new TestArmorStandFeature((ArmorStandEntityRenderer) entityRenderer));
}
// Obviously not recommended, just used for testing generics
registrationHelper.register(new ElytraFeatureRenderer<>(entityRenderer));
if (entityRenderer instanceof BipedEntityRenderer) {
// It works, method ref is encouraged
registrationHelper.register(new HeldItemFeatureRenderer<>((BipedEntityRenderer<?, ?>) entityRenderer));
}
});
LivingEntityFeatureRendererRegistrationCallback.EVENT.register(this::registerFeatures);
}
private void registerFeatures(EntityType<? extends LivingEntity> entityType, LivingEntityRenderer<?, ?> entityRenderer, LivingEntityFeatureRendererRegistrationCallback.RegistrationHelper registrationHelper) {
if (entityRenderer instanceof PlayerEntityRenderer) {
registrationHelper.register(new TestPlayerFeature((PlayerEntityRenderer) entityRenderer));
// This is T extends AbstractClientPlayerEntity
registrationHelper.register(new GenericTestPlayerFeature<>((PlayerEntityRenderer) entityRenderer));
}
if (entityRenderer instanceof ArmorStandEntityRenderer) {
registrationHelper.register(new TestArmorStandFeature((ArmorStandEntityRenderer) entityRenderer));
}
// Obviously not recommended, just used for testing generics.
registrationHelper.register(new ElytraFeatureRenderer<>(entityRenderer));
if (entityRenderer instanceof BipedEntityRenderer) {
// It works, method ref is encouraged
registrationHelper.register(new HeldItemFeatureRenderer<>((BipedEntityRenderer<?, ?>) entityRenderer));
}
}
static class TestPlayerFeature extends FeatureRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
TestPlayerFeature(FeatureRendererContext<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> context) {
super(context);
}
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, AbstractClientPlayerEntity entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) {
}
}
static class GenericTestPlayerFeature<T extends AbstractClientPlayerEntity, M extends PlayerEntityModel<T>> extends FeatureRenderer<T, M> {
GenericTestPlayerFeature(FeatureRendererContext<T, M> context) {
super(context);
}
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) {
}
}
static class TestArmorStandFeature extends FeatureRenderer<ArmorStandEntity, ArmorStandArmorEntityModel> {
TestArmorStandFeature(FeatureRendererContext<ArmorStandEntity, ArmorStandArmorEntityModel> context) {
super(context);
}
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, ArmorStandEntity entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) {
}
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.renderer.registry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.PlayerEntityRenderer;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import net.minecraft.client.render.entity.model.PlayerEntityModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.EntityType;
import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.fabricmc.fabric.api.client.rendereregistry.v1.LivingEntityFeatureRendererRegistrationCallback;
public final class FeatureRendererTest implements ClientModInitializer {
private static final Logger LOGGER = LogManager.getLogger(FeatureRendererTest.class);
private int playerRegistrations = 0;
@Override
public void onInitializeClient() {
LOGGER.info("Registering test feature renderer");
LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper) -> {
// minecraft:player SHOULD be printed twice
LOGGER.info(String.format("Received registration for %s", Registry.ENTITY_TYPE.getId(entityType)));
if (entityType == EntityType.PLAYER) {
this.playerRegistrations++;
}
if (entityRenderer instanceof PlayerEntityRenderer) {
registrationHelper.register(new TestPlayerFeatureRenderer((PlayerEntityRenderer) entityRenderer));
}
});
ClientLifecycleEvents.CLIENT_STARTED.register(client -> {
if (this.playerRegistrations != 2) {
throw new AssertionError(String.format("Expected 2 entity feature renderer registration events for \"minecraft:player\" but received %s registrations", this.playerRegistrations));
}
LOGGER.info("Successfully called feature renderer registration events");
});
}
private static class TestPlayerFeatureRenderer extends FeatureRenderer<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> {
TestPlayerFeatureRenderer(FeatureRendererContext<AbstractClientPlayerEntity, PlayerEntityModel<AbstractClientPlayerEntity>> context) {
super(context);
}
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, AbstractClientPlayerEntity entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) {
matrices.push();
// Translate to center above the player's head
matrices.translate(-0.5F, -entity.getHeight() + 0.25F, -0.5F);
// Render a diamond block above the player's head
MinecraftClient.getInstance().getBlockRenderManager().renderBlockAsEntity(Blocks.DIAMOND_BLOCK.getDefaultState(), matrices, vertexConsumers, light, OverlayTexture.DEFAULT_UV);
matrices.pop();
}
}
}

View file

@ -0,0 +1,16 @@
{
"schemaVersion": 1,
"id": "fabric-renderer-registries-v1-testmod",
"name": "Fabric Render Registries (v1) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-item-api-v1": "*"
},
"entrypoints": {
"client": [
"net.fabricmc.fabric.test.renderer.registry.FeatureRendererTest"
]
}
}