Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.21.3] RegisterRenderStateModifiersEvent for appending custom data to render state objects #1650

Open
wants to merge 51 commits into
base: 1.21.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
fab1b31
Draft for event
dhyces Oct 31, 2024
3139c92
Example 1
dhyces Nov 1, 2024
3c5e760
Example 2
dhyces Nov 1, 2024
99600ad
Cherry-pick changes from example 2 branch
dhyces Oct 31, 2024
f98e132
Apply Tslat's feedback
dhyces Nov 11, 2024
a8ef1cf
Rename to `RegisterRenderStateModifiersEvent`
dhyces Nov 11, 2024
b41cdf4
Spotless
dhyces Nov 11, 2024
9691504
Use appropriate class from the entry
dhyces Nov 11, 2024
b519839
Reset render data before computing modifiers
dhyces Nov 11, 2024
8643b7d
Update EntityRenderState.java.patch
dhyces Nov 12, 2024
48db639
Remove lazy map init
dhyces Nov 14, 2024
84dacf3
Merge branch '1.21.x' into feat/render-state-event
dhyces Nov 14, 2024
ea86bb1
Merge branch '1.21.x' into feat/render-state-event
dhyces Nov 19, 2024
c79f020
Update with proposed changes and add jdocs
dhyces Nov 19, 2024
d6f6d5e
Spotless
dhyces Nov 19, 2024
162cc71
Remove rather unnecessary interface indirection
dhyces Nov 19, 2024
2ff63e8
Missed patch generation
dhyces Nov 19, 2024
bc9fec4
Fix entity render states
dhyces Nov 19, 2024
432b640
Fail instead of throw
dhyces Nov 19, 2024
d15a61d
Add another fail condition
dhyces Nov 19, 2024
0548610
Add map render state modifiers
dhyces Nov 19, 2024
ec1d9fb
Spotless
dhyces Nov 19, 2024
07abe9f
Inline
dhyces Nov 19, 2024
9a70d17
Move some classes to specific package
dhyces Nov 19, 2024
fb09e53
Split out MapDecorationRenderStateModifier
dhyces Nov 19, 2024
346fc0b
Add override annotations
dhyces Nov 19, 2024
ed942c8
Move update methods into RenderStateExtensions
dhyces Nov 19, 2024
3fbf679
Move event into subpackage and reduce method vis
dhyces Nov 19, 2024
3194e3a
Apply formatting
dhyces Nov 19, 2024
8ad9a50
Generate patches
dhyces Nov 19, 2024
c1c76b8
Merge branch '1.21.x' into feat/render-state-event
dhyces Nov 19, 2024
075db02
Docs changes
dhyces Nov 20, 2024
4c44bf2
Simplify RenderStateExtensions
dhyces Nov 20, 2024
cdf43f7
Fix flipped method call
dhyces Nov 20, 2024
169c0d3
Update docs
dhyces Nov 20, 2024
67d86e6
Add living entity register method
dhyces Nov 21, 2024
1e24034
Some more jdoc changes
dhyces Nov 21, 2024
6e0cca3
Update to using TypeTokens
dhyces Nov 21, 2024
e057759
Apply formatting
dhyces Nov 21, 2024
532ced9
Adjust test scaling
dhyces Nov 21, 2024
1332b90
Remove redundant cast
dhyces Nov 21, 2024
b429629
Work on runtime type safety checks
dhyces Nov 21, 2024
1f34a18
Adjust type safety (big thanks to @lukebemish)
dhyces Nov 22, 2024
dc0bba0
Example usage docs
dhyces Nov 22, 2024
7f2e7fc
Convenience method
dhyces Nov 22, 2024
e00f760
Update test
dhyces Nov 23, 2024
54a5d6a
Merge branch '1.21.x' into feat/render-state-event
dhyces Nov 23, 2024
623bb3f
Apply feedback
dhyces Nov 24, 2024
d400b27
Merge branch 'feat/render-state-event' of https://github.com/dhyces/N…
dhyces Nov 24, 2024
fa02d48
Change which event group is used
dhyces Nov 24, 2024
dce2dc5
Merge branch '1.21.x' into feat/render-state-event
dhyces Nov 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion patches/net/minecraft/client/renderer/MapRenderer.java.patch
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@
p_362483_.pushPose();
p_362483_.translate(
(float)maprenderstate$mapdecorationrenderstate.x / 2.0F + 64.0F, (float)maprenderstate$mapdecorationrenderstate.y / 2.0F + 64.0F, -0.02F
@@ -116,6 +_,7 @@
@@ -109,13 +_,15 @@
p_364922_.texture = this.mapTextureManager.prepareMapTexture(p_361383_, p_363500_);
p_364922_.decorations.clear();

+ net.neoforged.neoforge.client.renderstate.RenderStateExtensions.onUpdateMapRenderState(p_363500_, p_364922_);
for (MapDecoration mapdecoration : p_363500_.getDecorations()) {
- p_364922_.decorations.add(this.extractDecorationRenderState(mapdecoration));
+ p_364922_.decorations.add(net.neoforged.neoforge.client.renderstate.RenderStateExtensions.onUpdateMapDecorationRenderState(mapdecoration.type(), p_363500_, p_364922_, this.extractDecorationRenderState(mapdecoration)));
}
}

private MapRenderState.MapDecorationRenderState extractDecorationRenderState(MapDecoration p_364175_) {
MapRenderState.MapDecorationRenderState maprenderstate$mapdecorationrenderstate = new MapRenderState.MapDecorationRenderState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
this.renderNameTag(p_364816_, p_364816_.nameTag, p_114488_, p_114489_, p_114490_);
}
}
@@ -245,6 +_,7 @@
public final S createRenderState(T p_361382_, float p_360885_) {
S s = this.reusedState;
this.extractRenderState(p_361382_, s, p_360885_);
+ net.neoforged.neoforge.client.renderstate.RenderStateExtensions.onUpdateEntityRenderState(this, p_361382_, s);
dhyces marked this conversation as resolved.
Show resolved Hide resolved
return s;
}

@@ -270,7 +_,12 @@
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
--- a/net/minecraft/client/renderer/entity/state/EntityRenderState.java
+++ b/net/minecraft/client/renderer/entity/state/EntityRenderState.java
@@ -7,7 +_,7 @@
import net.neoforged.api.distmarker.OnlyIn;

@OnlyIn(Dist.CLIENT)
-public class EntityRenderState {
+public class EntityRenderState extends net.neoforged.neoforge.client.renderstate.BaseRenderState {
public double x;
public double y;
public double z;
@@ -27,6 +_,7 @@
public Vec3 nameTagAttachment;
@Nullable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
--- a/net/minecraft/client/renderer/state/MapRenderState.java
+++ b/net/minecraft/client/renderer/state/MapRenderState.java
@@ -17,6 +_,7 @@
@@ -10,13 +_,14 @@
import net.neoforged.api.distmarker.OnlyIn;

@OnlyIn(Dist.CLIENT)
-public class MapRenderState {
+public class MapRenderState extends net.neoforged.neoforge.client.renderstate.BaseRenderState {
@Nullable
public ResourceLocation texture;
public final List<MapRenderState.MapDecorationRenderState> decorations = new ArrayList<>();

@OnlyIn(Dist.CLIENT)
public static class MapDecorationRenderState {
- public static class MapDecorationRenderState {
+ public static class MapDecorationRenderState extends net.neoforged.neoforge.client.renderstate.BaseRenderState {
+ public net.minecraft.core.Holder<net.minecraft.world.level.saveddata.maps.MapDecorationType> type;
@Nullable
public TextureAtlasSprite atlasSprite;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/net/neoforged/neoforge/client/ClientHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
import net.neoforged.neoforge.client.gui.GuiLayerManager;
import net.neoforged.neoforge.client.gui.map.MapDecorationRendererManager;
import net.neoforged.neoforge.client.model.data.ModelData;
import net.neoforged.neoforge.client.renderstate.RegisterRenderStateModifiersEvent;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.common.NeoForgeMod;
import net.neoforged.neoforge.forge.snapshots.ForgeSnapshotsModClient;
Expand Down Expand Up @@ -990,6 +991,7 @@ public static void initClientHooks(Minecraft mc, ReloadableResourceManager resou
ModLoader.postEvent(new RegisterClientReloadListenersEvent(resourceManager));
ModLoader.postEvent(new EntityRenderersEvent.RegisterLayerDefinitions());
ModLoader.postEvent(new EntityRenderersEvent.RegisterRenderers());
ModLoader.postEvent(new RegisterRenderStateModifiersEvent());
ClientTooltipComponentManager.init();
EntitySpectatorShaderManager.init();
ClientHooks.onRegisterKeyMappings(mc.options);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client.extensions;

import net.minecraft.util.context.ContextKey;
import net.neoforged.neoforge.client.renderstate.BaseRenderState;
import org.jetbrains.annotations.Nullable;

/**
* Extension class for render state objects. Implemented by {@link BaseRenderState} for
* simple class extension.
*/
public interface IRenderStateExtension {
XFactHD marked this conversation as resolved.
Show resolved Hide resolved
/**
* Gets the object associated with the given key.
*
* @param key Static key reference object
* @return The object associated with the key or null if the key is not present.
* @param <T> Type of render data
*/
@Nullable
<T> T getRenderData(ContextKey<T> key);

/**
* Sets the object associated with the given key. Key should be stored statically for later retrieval of the object.
*
* @param key Static key reference object
* @param data Object to store for custom rendering
* @param <T> Type of render data
*/
<T> void setRenderData(ContextKey<T> key, @Nullable T data);

/**
* Gets the value or throws an exception. Should be used in cases where the data must be present.
*
* @param key Static key reference object
* @return The data associate with the key
* @param <T> Type of render data
*/
default <T> T getRenderDataOrThrow(ContextKey<T> key) {
T data = getRenderData(key);
if (data == null) {
throw new IllegalStateException("No value associated for key " + key);
}
return data;
}

/**
* Gets the value or returns the default object if an object is not present
*
* @param key Static key reference object
* @param defaultVal Default value if an object is not present
* @return Value from the render data or the given default value if value is not present
* @param <T> Type of render data
*/
default <T> T getRenderDataOrDefault(ContextKey<T> key, T defaultVal) {
T data = getRenderData(key);
if (data == null) {
return defaultVal;
}
return data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client.renderstate;

import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import java.util.Map;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.util.context.ContextKey;
import net.neoforged.neoforge.client.extensions.IRenderStateExtension;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

/**
* Extension class for RenderState objects (ie {@link EntityRenderState}).
* Allows modders to add arbitrary data onto render states for use in custom rendering.
*/
public abstract class BaseRenderState implements IRenderStateExtension {
protected Map<ContextKey<?>, Object> extensions = new Reference2ObjectOpenHashMap<>();

@SuppressWarnings("unchecked")
@Nullable
@Override
public <T> T getRenderData(ContextKey<T> key) {
return (T) extensions.get(key);
}

@Override
public <T> void setRenderData(ContextKey<T> key, @Nullable T data) {
if (data != null) {
extensions.put(key, data);
} else {
extensions.remove(key);
}
}

@ApiStatus.Internal
public void resetRenderData() {
extensions.clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client.renderstate;

import net.minecraft.client.renderer.state.MapRenderState;
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
import net.neoforged.neoforge.client.gui.map.IMapDecorationRenderer;

/**
* Function interface for render state modifiers that target MapDecorations. Useful for adding custom data for rendering
* in {@link IMapDecorationRenderer}s.
*/
@FunctionalInterface
public interface MapDecorationRenderStateModifier {
dhyces marked this conversation as resolved.
Show resolved Hide resolved
/**
* Called when the registered {@link MapDecorationType} is added to a {@link MapRenderState}.
*
* @param mapItemSavedData The map SavedData.
* @param mapRenderState The render state of the map after the texture has been set and custom data is added.
* @param mapDecorationRenderState The decoration render state after vanilla has set it up.
*/
void accept(MapItemSavedData mapItemSavedData, MapRenderState mapRenderState, MapRenderState.MapDecorationRenderState mapDecorationRenderState);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client.renderstate;

import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;
import java.lang.reflect.ParameterizedType;
import java.util.function.BiConsumer;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.client.renderer.state.MapRenderState;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.context.ContextKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
import net.neoforged.bus.api.Event;
import net.neoforged.fml.LogicalSide;
import net.neoforged.fml.event.IModBusEvent;
import net.neoforged.neoforge.client.extensions.IRenderStateExtension;
import org.jetbrains.annotations.ApiStatus;

/**
* Fired for registering modifier functions for various render state objects. Useful for gathering context for
* custom rendering with objects that are not your own.
*
* <p>This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterRenderStateModifiersEvent extends Event implements IModBusEvent {
@ApiStatus.Internal
public RegisterRenderStateModifiersEvent() {}

/**
* Registers a render state modifier for {@link EntityRenderState}s which are run after all vanilla data is
* extracted. Can add custom data to the map using {@link EntityRenderState#setRenderData(ContextKey, Object)}.
* Any subclasses of the passed renderer class will also have this modifier applied.
*
* <pre>
* <code>
* event.registerEntityModifier(new TypeToken<LivingEntityRenderer<LivingEntity, LivingEntityRenderState, ?>>() {}, (entity, renderState) -> {
* . . .
* });
* </code>
* </pre>
*
* @param baseRenderer Entity renderer class. Any subclasses will also apply this modifier.
* @param modifier The function for modifying the {@link EntityRenderState} and adding custom render data.
* @param <E> The type of the entity
* @param <S> The specific render state type
*/
public <E extends Entity, S extends EntityRenderState> void registerEntityModifier(TypeToken<? extends EntityRenderer<? extends E, ? extends S>> baseRenderer, BiConsumer<E, S> modifier) {
ensureParametersMatchBounds(baseRenderer);
RenderStateExtensions.registerEntity(baseRenderer.getRawType(), modifier);
dhyces marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Convenience method for cases where generics are not present. Registers a render state modifier for
* {@link EntityRenderState}s which are run after all vanilla data is extracted. Can add custom data to the map
* using {@link EntityRenderState#setRenderData(ContextKey, Object)}. Any subclasses of the passed renderer class
* will also have this modifier applied.
*
* <pre>
* <code>
* event.registerEntityModifier(PlayerRenderer.class, (entity, renderState) -> {
* . . .
* });
* </code>
* </pre>
*
* @param baseRenderer Entity renderer class. Any subclasses will also apply this modifier.
* @param modifier The function for modifying the {@link EntityRenderState} and adding custom render data.
* @param <E> The type of the entity
* @param <S> The specific render state type
*/
public <E extends Entity, S extends EntityRenderState> void registerEntityModifier(Class<? extends EntityRenderer<? extends E, ? extends S>> baseRenderer, BiConsumer<E, S> modifier) {
ensureParametersMatchBounds(TypeToken.of(baseRenderer));
RenderStateExtensions.registerEntity(baseRenderer, modifier);
}

/**
* Registers a render state modifier for {@link MapRenderState}s which are run after the texture has been set
* and before decorations have been added. Can add custom data to the map using
* {@link IRenderStateExtension#setRenderData(ContextKey, Object)}.
*
* @param modifier The function for modifying the {@link net.minecraft.client.renderer.state.MapRenderState} and adding custom render data.
*/
public void registerMapModifier(BiConsumer<MapItemSavedData, MapRenderState> modifier) {
RenderStateExtensions.registerMap(modifier);
}

/**
* Registers a render state modifier for {@link MapRenderState.MapDecorationRenderState}s which are run after
* vanilla map decoration data has been set. Can add custom data to the map using
* {@link IRenderStateExtension#setRenderData(ContextKey, Object)}.
*
* @param mapDecorationTypeKey Key for the registered {@link MapDecorationType}
* @param modifier The function for modifying the {@link MapRenderState.MapDecorationRenderState} and adding custom render data.
*/
public void registerMapDecorationModifier(ResourceKey<MapDecorationType> mapDecorationTypeKey, MapDecorationRenderStateModifier modifier) {
RenderStateExtensions.registerMapDecoration(mapDecorationTypeKey, modifier);
}

private static void ensureParametersMatchBounds(TypeToken<? extends EntityRenderer<? extends Entity, ? extends EntityRenderState>> baseRenderer) {
if (baseRenderer.getType() instanceof ParameterizedType parameterizedType) {
Class<?> bound = baseRenderer.getRawType();
ParameterizedType parameterized = parameterizedType;
do {
var userArgs = parameterized.getActualTypeArguments();
var typeArgs = bound.getTypeParameters();

for (int i = 0; i < userArgs.length; i++) {
var userArg = userArgs[i];
var userToken = Container.of(TypeToken.of(userArg));
var typeArg = typeArgs[i];
for (var singleBound : typeArg.getBounds()) {
var token = Container.of(TypeToken.of(singleBound));
if (!token.isSubtypeOf(userToken)) {
throw new IllegalArgumentException("%s does not match expected type parameter %s".formatted(userArg, singleBound));
}
}
}

if (!(parameterized.getOwnerType() instanceof ParameterizedType parameterizedOwner)) {
break;
}
parameterized = parameterizedOwner;
bound = bound.getEnclosingClass();
} while (bound != null);
}
}

@SuppressWarnings("unused")
private record Container<X>() {
private static <Z> TypeToken<Container<Z>> of(TypeToken<Z> parameter) {
return new TypeToken<Container<Z>>() {}
.where(new TypeParameter<>() {}, parameter);
}
}
}
Loading