diff --git a/patches/net/minecraft/client/multiplayer/ClientDebugSubscriber.java.patch b/patches/net/minecraft/client/multiplayer/ClientDebugSubscriber.java.patch new file mode 100644 index 0000000000..62e2657c96 --- /dev/null +++ b/patches/net/minecraft/client/multiplayer/ClientDebugSubscriber.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/client/multiplayer/ClientDebugSubscriber.java ++++ b/net/minecraft/client/multiplayer/ClientDebugSubscriber.java +@@ -64,6 +_,8 @@ + addFlag(subscriptions, DebugSubscriptions.VILLAGE_SECTIONS, SharedConstants.DEBUG_VILLAGE_SECTIONS); + } + ++ // Neo: Allow requesting custom subscriptions ++ net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.client.event.AddDebugSubscriptionFlagsEvent((subscription, flag) -> addFlag(subscriptions, subscription, flag))); + return subscriptions; + } + diff --git a/patches/net/minecraft/client/renderer/debug/DebugRenderer.java.patch b/patches/net/minecraft/client/renderer/debug/DebugRenderer.java.patch new file mode 100644 index 0000000000..580ea21a51 --- /dev/null +++ b/patches/net/minecraft/client/renderer/debug/DebugRenderer.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/client/renderer/debug/DebugRenderer.java ++++ b/net/minecraft/client/renderer/debug/DebugRenderer.java +@@ -137,6 +_,9 @@ + } + + this.renderers.add(new ChunkCullingDebugRenderer(minecraft)); ++ ++ // Neo: Allow registering custom debug renderers ++ net.neoforged.fml.ModLoader.postEvent(new net.neoforged.neoforge.client.event.RegisterDebugRenderersEvent(factory -> renderers.add(factory.apply(minecraft)))); + } + + public void emitGizmos(Frustum frustum, double camX, double camY, double camZ, float partialTicks) { diff --git a/src/client/java/net/neoforged/neoforge/client/event/AddDebugSubscriptionFlagsEvent.java b/src/client/java/net/neoforged/neoforge/client/event/AddDebugSubscriptionFlagsEvent.java new file mode 100644 index 0000000000..4a138f1f75 --- /dev/null +++ b/src/client/java/net/neoforged/neoforge/client/event/AddDebugSubscriptionFlagsEvent.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.event; + +import it.unimi.dsi.fastutil.objects.ObjectBooleanBiConsumer; +import java.util.function.Supplier; +import net.minecraft.client.multiplayer.ClientDebugSubscriber; +import net.minecraft.util.debug.DebugSubscription; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.LogicalSide; +import net.neoforged.fml.loading.FMLEnvironment; +import org.jetbrains.annotations.ApiStatus; + +/** + * This event allows mods to register client-side {@linkplain DebugSubscription debug subscription} flags. + *

+ * These flags are used to determine when subscriptions are allowed to register and store debug renderer values. + * If no flag is registered for a given debug subscription then it will be treated as a {@code always-disabled} flag and no debug values will be stored for it. + *

+ * This event is fired once per tick during {@linkplain ClientDebugSubscriber#requestedSubscriptions()}. + *

+ * This event is fired on the {@linkplain LogicalSide#CLIENT logical client}. + * + * @apiNote Each {@linkplain DebugSubscription debug subscription} should only be registered once. + */ +public final class AddDebugSubscriptionFlagsEvent extends Event { + private final ObjectBooleanBiConsumer> registrar; + + @ApiStatus.Internal + public AddDebugSubscriptionFlagsEvent(ObjectBooleanBiConsumer> registrar) { + this.registrar = registrar; + } + + /** + * Register a new flag for the given {@linkplain DebugSubscription debug subscription}. + * + * @param subscription {@linkplain DebugSubscription debug subscription} to register flag for. + * @param flag Flag used to conditionally enable or disable the given {@linkplain DebugSubscription debug subscription}. + */ + public void addFlag(DebugSubscription subscription, boolean flag) { + registrar.accept(subscription, flag); + } + + /** + * Register a new flag for the given {@linkplain DebugSubscription debug subscription}. + * + * @param subscription {@linkplain DebugSubscription Debug subscription} to register flag for. + * @param flag Flag used to conditionally enable or disable the given {@linkplain DebugSubscription debug subscription}. + */ + public void addFlag(Supplier> subscription, boolean flag) { + addFlag(subscription.get(), flag); + } + + /** + * Mark the given {@linkplain DebugSubscription debug subscription} as only active in dev. + *

+ * Using this method will mark your {@linkplain DebugSubscription debug subscription} as only being enabled in dev a.k.a when {@linkplain FMLEnvironment#isProduction()} == {@code false}. + * + * @param subscription {@linkplain DebugSubscription debug subscription} to register flag for. + */ + public void addActiveInDev(DebugSubscription subscription) { + addFlag(subscription, !FMLEnvironment.isProduction()); + } + + /** + * Mark the given {@linkplain DebugSubscription debug subscription} as only active in dev. + *

+ * Using this method will mark your {@linkplain DebugSubscription debug subscription} as only being enabled in dev a.k.a when {@linkplain FMLEnvironment#isProduction()} == {@code false}. + * + * @param subscription {@linkplain DebugSubscription Debug subscription} to register flag for. + */ + public void addActiveInDev(Supplier> subscription) { + addActiveInDev(subscription.get()); + } +} diff --git a/src/client/java/net/neoforged/neoforge/client/event/RegisterDebugRenderersEvent.java b/src/client/java/net/neoforged/neoforge/client/event/RegisterDebugRenderersEvent.java new file mode 100644 index 0000000000..ee2d87358c --- /dev/null +++ b/src/client/java/net/neoforged/neoforge/client/event/RegisterDebugRenderersEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.event; + +import java.util.function.Consumer; +import java.util.function.Function; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.debug.DebugRenderer; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.LogicalSide; +import net.neoforged.fml.event.IModBusEvent; +import org.jetbrains.annotations.ApiStatus; + +/** + * This event allows mods to register custom {@link DebugRenderer.SimpleDebugRenderer}s. + *

+ * This event is fired during {@link DebugRenderer#refreshRendererList()}. + *

+ * This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}. + */ +public final class RegisterDebugRenderersEvent extends Event implements IModBusEvent { + private final Consumer> registrar; + + @ApiStatus.Internal + public RegisterDebugRenderersEvent(Consumer> registrar) { + this.registrar = registrar; + } + + /** + * Registers the given debug renderer + * + * @param factory Factory used to construct the debug renderer + */ + public void register(Function factory) { + registrar.accept(factory); + } + + /** + * Registers the given debug renderer + * + * @param renderer Debug renderer to be registered + */ + public void register(DebugRenderer.SimpleDebugRenderer renderer) { + register(client -> renderer); + } +} diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/CustomDebugSubscriberTest.java b/tests/src/main/java/net/neoforged/neoforge/debug/CustomDebugSubscriberTest.java new file mode 100644 index 0000000000..29184b01b0 --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/debug/CustomDebugSubscriberTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.debug; + +import com.mojang.serialization.MapCodec; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.renderer.debug.DebugRenderer; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.gizmos.Gizmos; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.CommonColors; +import net.minecraft.util.debug.DebugSubscription; +import net.minecraft.util.debug.DebugValueAccess; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.world.phys.BlockHitResult; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.neoforge.client.event.AddDebugSubscriptionFlagsEvent; +import net.neoforged.neoforge.client.event.RegisterDebugRenderersEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.testframework.DynamicTest; +import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.registration.RegistrationHelper; + +@ForEachTest(groups = "custom_debug_subscribers", side = Dist.CLIENT) +public interface CustomDebugSubscriberTest { + @TestHolder(description = "Renders debug info for a new block entity using debug renderers and subscribers", enabledByDefault = true) + static void testDebugSubscriptions(DynamicTest test, RegistrationHelper reg) { + var id = "debug_subscription"; + var registryName = Identifier.fromNamespaceAndPath(reg.modId(), id); + var blockEntityType = DeferredHolder.create(Registries.BLOCK_ENTITY_TYPE, registryName); + + // register our custom debug subscription + var debugSubscription = reg.registrar(Registries.DEBUG_SUBSCRIPTION).register(id, () -> new DebugSubscription<>(ByteBufCodecs.VAR_INT, 200)); + + final class DebugBlockEntity extends BlockEntity { + private int counter = 0; + + private DebugBlockEntity(BlockPos pos, BlockState blockState) { + super(blockEntityType.value(), pos, blockState); + } + + public void incrementCounter() { + counter++; + setChanged(); + } + + @Override + protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); + output.putInt("counter", counter); + } + + @Override + protected void loadAdditional(ValueInput input) { + super.loadAdditional(input); + counter = input.getIntOr("counter", 0); + } + + @Override + public void registerDebugValues(ServerLevel level, Registration registration) { + // mark this entity for displaying debug info for our subscription + registration.register(debugSubscription.value(), () -> counter); + } + } + + // generic block for our block entity + final class DebugBlock extends BaseEntityBlock { + private DebugBlock(Properties properties) { + super(properties); + } + + @Override + protected MapCodec codec() { + return null; + } + + @Override + protected InteractionResult useWithoutItem(BlockState blockState, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { + if (!(level.getBlockEntity(pos) instanceof DebugBlockEntity blockEntity)) { + return InteractionResult.FAIL; + } + + blockEntity.incrementCounter(); + return InteractionResult.SUCCESS; + } + + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState blockState) { + return new DebugBlockEntity(pos, blockState); + } + } + + var block = reg.blocks().registerBlock(id, DebugBlock::new); + reg.items().registerSimpleBlockItem(block); + reg.registrar(Registries.BLOCK_ENTITY_TYPE).register(id, () -> new BlockEntityType<>(DebugBlockEntity::new, block.value())); + + // debug renderer used to render counters above our block entities + final class DebugCountRenderer implements DebugRenderer.SimpleDebugRenderer { + private DebugCountRenderer(Minecraft client) {} + + @Override + public void emitGizmos(double camX, double camY, double camZ, DebugValueAccess valueAccess, Frustum frustum, float partialTicks) { + // list is populated via 'DebugBlockEntity.registerDebugValues' + // while we are using block entities this, you can also register subscribers for entities and chunks too + valueAccess.forEachBlock(debugSubscription.value(), (pos, count) -> { + Gizmos.billboardTextOverBlock(count + "", pos, 0, CommonColors.WHITE, .5F); + }); + } + } + + // mark our subscriber as only being active in dev + NeoForge.EVENT_BUS.addListener(AddDebugSubscriptionFlagsEvent.class, event -> event.addActiveInDev(debugSubscription.value())); + + // register our debug renderer + reg.eventListeners().accept((RegisterDebugRenderersEvent event) -> event.register(DebugCountRenderer::new)); + + test.pass(); + } +}