Skip to content

Commit

Permalink
BlockView API v2 (#3268)
Browse files Browse the repository at this point in the history
* Fabric BlockView API v2

* Fix dependency on nonexistent module

* Add test for biome getter

* Improve getBiomeFabric documentation

* Simplify javadoc

(cherry picked from commit 92a0d36)
  • Loading branch information
PepperCode1 authored and modmuss50 committed Sep 3, 2023
1 parent ceabd76 commit 73761d2
Show file tree
Hide file tree
Showing 39 changed files with 746 additions and 249 deletions.
3 changes: 3 additions & 0 deletions deprecated/fabric-rendering-data-attachment-v1/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
version = getSubprojectVersion(project)

moduleDependencies(project, ['fabric-block-view-api-v2'])
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.rendering.data.v1;

import org.jetbrains.annotations.Nullable;

import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.WorldView;

import net.fabricmc.fabric.api.blockview.v2.FabricBlockView;

/**
* This interface is guaranteed to be implemented on all {@link WorldView} instances.
* It is likely to be implemented on any given {@link BlockRenderView} instance, but
* this is not guaranteed.
*
* @deprecated Use {@link FabricBlockView} instead.
*/
@Deprecated
public interface RenderAttachedBlockView extends BlockRenderView {
/**
* This method will call {@link FabricBlockView#getBlockEntityRenderData(BlockPos)} by default.
*
* @deprecated Use {@link FabricBlockView#getBlockEntityRenderData(BlockPos)} instead.
*/
@Deprecated
@Nullable
default Object getBlockEntityRenderAttachment(BlockPos pos) {
return getBlockEntityRenderData(pos);
}
}
Original file line number Diff line number Diff line change
@@ -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.api.rendering.data.v1;

import org.jetbrains.annotations.Nullable;

import net.minecraft.block.entity.BlockEntity;

import net.fabricmc.fabric.api.blockview.v2.RenderDataBlockEntity;

/**
* This interface is guaranteed to be implemented on all {@link BlockEntity} instances.
*
* @deprecated Use {@link RenderDataBlockEntity} instead.
*/
@Deprecated
@FunctionalInterface
public interface RenderAttachmentBlockEntity {
/**
* This method will be automatically called if {@link RenderDataBlockEntity#getRenderData()} is not overridden.
*
* @deprecated Use {@link RenderDataBlockEntity#getRenderData()} instead.
*/
@Deprecated
@Nullable
Object getRenderAttachmentData();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.rendering.data;

import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;

import net.minecraft.block.entity.BlockEntity;

import net.fabricmc.fabric.api.blockview.v2.RenderDataBlockEntity;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity;

@Mixin(BlockEntity.class)
public class BlockEntityMixin implements RenderAttachmentBlockEntity, RenderDataBlockEntity {
@Override
@Nullable
public Object getRenderAttachmentData() {
return null;
}

/**
* Instead of returning null by default in v2, proxy to v1 method instead.
*/
@Override
@Nullable
public Object getRenderData() {
return getRenderAttachmentData();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@
* limitations under the License.
*/

package net.fabricmc.fabric.mixin.rendering.data.attachment;
package net.fabricmc.fabric.mixin.rendering.data;

import org.spongepowered.asm.mixin.Mixin;

import net.minecraft.world.BlockRenderView;
import net.minecraft.world.WorldView;

import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;

/** Make {@link BlockRenderView} implement {@link RenderAttachedBlockView}. */
@Mixin(WorldView.class)
public interface WorldViewMixin extends RenderAttachedBlockView { }
public interface WorldViewMixin extends RenderAttachedBlockView {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.rendering.data.attachment",
"compatibilityLevel": "JAVA_16",
"package": "net.fabricmc.fabric.mixin.rendering.data",
"compatibilityLevel": "JAVA_17",
"mixins": [
"BlockEntityMixin",
"WorldViewMixin"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,13 @@
],
"depends": {
"fabricloader": ">=0.4.0",
"fabric-api-base": "*"
"fabric-block-view-api-v2": "*"
},
"description": "Thread-safe hooks for block entity data use during terrain rendering.",
"mixins": [
"fabric-rendering-data-attachment-v1.mixins.json",
{
"config": "fabric-rendering-data-attachment-v1.client.mixins.json",
"environment": "client"
}
"fabric-rendering-data-attachment-v1.mixins.json"
],
"custom": {
"fabric-api:module-lifecycle": "stable"
},
"accessWidener": "fabric-rendering-data-attachment-v1.accesswidener"
"fabric-api:module-lifecycle": "deprecated"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,21 @@ public interface FabricBlock {
* <p>This can be called on the server, where block entity data can be safely accessed,
* and on the client, possibly in a meshing thread, where block entity data is not safe to access!
* Here is an example of how data from a block entity can be handled safely.
* The block entity needs to implement {@code RenderAttachmentBlockEntity} for this to work.
* The block entity should override {@code RenderDataBlockEntity#getBlockEntityRenderData} to return
* the necessary data. Refer to the documentation of {@code RenderDataBlockEntity} for more information.
* <pre>{@code @Override
* public BlockState getAppearance(BlockState state, BlockRenderView renderView, BlockPos pos, Direction side, @Nullable BlockState sourceState, @Nullable BlockPos sourcePos) {
* if (renderView instanceof ServerWorld serverWorld) {
* // Server side, ok to use block entity directly!
* // Server side; ok to use block entity directly!
* BlockEntity blockEntity = serverWorld.getBlockEntity(pos);
*
* if (blockEntity instanceof ...) {
* // Get data from block entity
* return ...;
* }
* } else {
* // Client side, need to use the render attachment!
* RenderAttachedBlockView attachmentView = (RenderAttachedBlockView) renderView;
* Object data = attachmentView.getBlockEntityRenderAttachment(pos);
* // Client side; need to use the block entity render data!
* Object data = renderView.getBlockEntityRenderData(pos);
*
* // Check if data is not null and of the correct type, and use that to determine the appearance
* if (data instanceof ...) {
Expand Down
5 changes: 5 additions & 0 deletions fabric-block-view-api-v2/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version = getSubprojectVersion(project)

loom {
accessWidenerPath = file("src/main/resources/fabric-block-view-api-v2.accesswidener")
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
* limitations under the License.
*/

package net.fabricmc.fabric.impl.rendering.data.attachment;
package net.fabricmc.fabric.impl.blockview.client;

import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;

public interface RenderDataObjectConsumer {
void fabric_acceptRenderDataObjects(Long2ObjectOpenHashMap<Object> renderDataObjects);
public interface RenderDataMapConsumer {
void fabric_acceptRenderDataMap(Long2ObjectMap<Object> renderDataMap);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,97 +14,102 @@
* limitations under the License.
*/

package net.fabricmc.fabric.mixin.rendering.data.attachment.client;
package net.fabricmc.fabric.mixin.blockview.client;

import java.util.ConcurrentModificationException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;

import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.render.chunk.ChunkRendererRegionBuilder;
import net.minecraft.client.render.chunk.ChunkRendererRegion;
import net.minecraft.client.render.chunk.ChunkRendererRegionBuilder;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.WorldChunk;

import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity;
import net.fabricmc.fabric.impl.rendering.data.attachment.RenderDataObjectConsumer;
import net.fabricmc.fabric.impl.blockview.client.RenderDataMapConsumer;

@Mixin(ChunkRendererRegionBuilder.class)
public abstract class ChunkRendererRegionBuilderMixin {
private static final AtomicInteger ERROR_COUNTER = new AtomicInteger();
private static final Logger LOGGER = LoggerFactory.getLogger(ChunkRendererRegionBuilderMixin.class);

@Inject(at = @At("RETURN"), method = "build", locals = LocalCapture.CAPTURE_FAILHARD)
private void create(World world, BlockPos startPos, BlockPos endPos, int chunkRadius, CallbackInfoReturnable<ChunkRendererRegion> info, int i, int j, int k, int l, ChunkRendererRegionBuilder.ClientChunk[][] chunkData) {
@Inject(method = "build", at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD)
private void createDataMap(World world, BlockPos startPos, BlockPos endPos, int offset, CallbackInfoReturnable<ChunkRendererRegion> cir, int startX, int startZ, int endX, int endZ, ChunkRendererRegionBuilder.ClientChunk[][] chunksXZ) {
ChunkRendererRegion rendererRegion = cir.getReturnValue();

if (rendererRegion == null) {
return;
}

// instantiated lazily - avoids allocation for chunks without any data objects - which is most of them!
Long2ObjectOpenHashMap<Object> map = null;

for (ChunkRendererRegionBuilder.ClientChunk[] chunkDataOuter : chunkData) {
for (ChunkRendererRegionBuilder.ClientChunk data : chunkDataOuter) {
for (ChunkRendererRegionBuilder.ClientChunk[] chunksZ : chunksXZ) {
for (ChunkRendererRegionBuilder.ClientChunk chunk : chunksZ) {
// Hash maps in chunks should generally not be modified outside of client thread
// but does happen in practice, due to mods or inconsistent vanilla behaviors, causing
// CMEs when we iterate the map. (Vanilla does not iterate these maps when it builds
// CMEs when we iterate the map. (Vanilla does not iterate these maps when it builds
// the chunk cache and does not suffer from this problem.)
//
// We handle this simply by retrying until it works. Ugly but effective.
for (;;) {
// We handle this simply by retrying until it works. Ugly but effective.
while (true) {
try {
map = mapChunk(data.getChunk(), startPos, endPos, map);
map = mapChunk(chunk.getChunk(), startPos, endPos, map);
break;
} catch (ConcurrentModificationException e) {
final int count = ERROR_COUNTER.incrementAndGet();

if (count <= 5) {
LOGGER.warn("[Render Data Attachment] Encountered CME during render region build. A mod is accessing or changing chunk data outside the main thread. Retrying.", e);
LOGGER.warn("[Block Entity Render Data] Encountered CME during render region build. A mod is accessing or changing chunk data outside the main thread. Retrying.", e);

if (count == 5) {
LOGGER.info("[Render Data Attachment] Subsequent exceptions will be suppressed.");
LOGGER.info("[Block Entity Render Data] Subsequent exceptions will be suppressed.");
}
}
}
}
}
}

ChunkRendererRegion rendererRegion = info.getReturnValue();

if (map != null && rendererRegion != null) {
((RenderDataObjectConsumer) rendererRegion).fabric_acceptRenderDataObjects(map);
if (map != null) {
((RenderDataMapConsumer) rendererRegion).fabric_acceptRenderDataMap(map);
}
}

@Unique
private static Long2ObjectOpenHashMap<Object> mapChunk(WorldChunk chunk, BlockPos posFrom, BlockPos posTo, Long2ObjectOpenHashMap<Object> map) {
final int xMin = posFrom.getX();
final int xMax = posTo.getX();
final int zMin = posFrom.getZ();
final int zMax = posTo.getZ();
final int yMin = posFrom.getY();
final int yMax = posTo.getY();
final int zMin = posFrom.getZ();
final int zMax = posTo.getZ();

for (Map.Entry<BlockPos, BlockEntity> entry : chunk.getBlockEntities().entrySet()) {
final BlockPos entPos = entry.getKey();
final BlockPos pos = entry.getKey();

if (entPos.getX() >= xMin && entPos.getX() <= xMax
&& entPos.getY() >= yMin && entPos.getY() <= yMax
&& entPos.getZ() >= zMin && entPos.getZ() <= zMax) {
final Object o = ((RenderAttachmentBlockEntity) entry.getValue()).getRenderAttachmentData();
if (pos.getX() >= xMin && pos.getX() <= xMax
&& pos.getY() >= yMin && pos.getY() <= yMax
&& pos.getZ() >= zMin && pos.getZ() <= zMax) {
final Object data = entry.getValue().getRenderData();

if (o != null) {
if (data != null) {
if (map == null) {
map = new Long2ObjectOpenHashMap<>();
}

map.put(entPos.asLong(), o);
map.put(pos.asLong(), data);
}
}
}
Expand Down
Loading

0 comments on commit 73761d2

Please sign in to comment.