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

BlockView API v2 #3268

Merged
merged 5 commits into from
Sep 3, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions deprecated/fabric-rendering-data-attachment-v1/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
archivesBaseName = "fabric-rendering-data-attachment-v1"
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
6 changes: 6 additions & 0 deletions fabric-block-view-api-v2/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
archivesBaseName = "fabric-block-view-api-v2"
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