Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -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;
}

Original file line number Diff line number Diff line change
@@ -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) {
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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.
* <p>
* This event is fired once per tick during {@linkplain ClientDebugSubscriber#requestedSubscriptions()}.
* <p>
* 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<DebugSubscription<?>> registrar;

@ApiStatus.Internal
public AddDebugSubscriptionFlagsEvent(ObjectBooleanBiConsumer<DebugSubscription<?>> 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<? extends DebugSubscription<?>> subscription, boolean flag) {
addFlag(subscription.get(), flag);
}

/**
* Mark the given {@linkplain DebugSubscription debug subscription} as only active in dev.
* <p>
* 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.
* <p>
* 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<? extends DebugSubscription<?>> subscription) {
addActiveInDev(subscription.get());
}
}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* This event is fired during {@link DebugRenderer#refreshRendererList()}.
* <p>
* 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<Function<Minecraft, DebugRenderer.SimpleDebugRenderer>> registrar;

@ApiStatus.Internal
public RegisterDebugRenderersEvent(Consumer<Function<Minecraft, DebugRenderer.SimpleDebugRenderer>> registrar) {
this.registrar = registrar;
}

/**
* Registers the given debug renderer
*
* @param factory Factory used to construct the debug renderer
*/
public void register(Function<Minecraft, DebugRenderer.SimpleDebugRenderer> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<? extends BaseEntityBlock> 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();
}
}
Loading