BlockView API v2 (#3268)

* Fabric BlockView API v2

* Fix dependency on nonexistent module

* Add test for biome getter

* Improve getBiomeFabric documentation

* Simplify javadoc

(cherry picked from commit 92a0d36746)
This commit is contained in:
PepperCode1 2023-09-03 13:06:21 +01:00 committed by modmuss50
parent ceabd7613a
commit 73761d2e2d
39 changed files with 746 additions and 249 deletions

View file

@ -0,0 +1,3 @@
version = getSubprojectVersion(project)
moduleDependencies(project, ['fabric-block-view-api-v2'])

View file

@ -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);
}
}

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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 {
}

View file

@ -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"

View file

@ -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"
}
}

View file

@ -56,11 +56,12 @@ 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 ...) {
@ -68,9 +69,8 @@ public interface FabricBlock {
* 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 ...) {

View file

@ -0,0 +1,5 @@
version = getSubprojectVersion(project)
loom {
accessWidenerPath = file("src/main/resources/fabric-block-view-api-v2.accesswidener")
}

View file

@ -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);
}

View file

@ -14,61 +14,67 @@
* 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.");
}
}
}
@ -76,35 +82,34 @@ public abstract class ChunkRendererRegionBuilderMixin {
}
}
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);
}
}
}

View file

@ -0,0 +1,67 @@
/*
* 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.blockview.client;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import net.minecraft.client.render.chunk.ChunkRendererRegion;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.fabricmc.fabric.impl.blockview.client.RenderDataMapConsumer;
@Mixin(ChunkRendererRegion.class)
public abstract class ChunkRendererRegionMixin implements BlockRenderView, RenderDataMapConsumer {
@Shadow
@Final
protected World world;
@Unique
@Nullable
private Long2ObjectMap<Object> fabric_renderDataMap;
@Override
public Object getBlockEntityRenderData(BlockPos pos) {
return fabric_renderDataMap == null ? null : fabric_renderDataMap.get(pos.asLong());
}
/**
* Called in {@link ChunkRendererRegionBuilderMixin}.
*/
@Override
public void fabric_acceptRenderDataMap(Long2ObjectMap<Object> renderDataMap) {
this.fabric_renderDataMap = renderDataMap;
}
@Override
public boolean hasBiomes() {
return true;
}
@Override
public RegistryEntry<Biome> getBiomeFabric(BlockPos pos) {
return world.getBiome(pos);
}
}

View file

@ -1,7 +1,7 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.rendering.data.attachment.client",
"compatibilityLevel": "JAVA_16",
"package": "net.fabricmc.fabric.mixin.blockview.client",
"compatibilityLevel": "JAVA_17",
"client": [
"ChunkRendererRegionMixin",
"ChunkRendererRegionBuilderMixin"

View file

@ -0,0 +1,104 @@
/*
* 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.blockview.v2;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
import net.minecraft.world.WorldView;
import net.minecraft.world.biome.Biome;
/**
* General-purpose Fabric-provided extensions for {@link BlockView} subclasses.
*
* <p>These extensions were designed primarily for use by methods invoked during chunk building, but
* they can also be used in other contexts.
*
* <p>Note: This interface is automatically implemented on all {@link BlockView} instances via Mixin and interface injection.
*/
public interface FabricBlockView {
/**
* Retrieves block entity render data for a given block position.
*
* <p>This method must be used instead of {@link BlockView#getBlockEntity(BlockPos)} in cases
* where the user knows that the current context may be multithreaded, such as chunk building, to
* ensure thread safety and data consistency. Using a {@link BlockEntity} directly may not be
* thread-safe since it may lead to non-atomic modification of the internal state of the
* {@link BlockEntity} (such as through lazy computation). Using a {@link BlockEntity} directly
* may not be consistent since the internal state of the {@link BlockEntity} may change on a
* different thread.
*
* <p>As previously stated, a common environment to use this method in is chunk building. Methods
* that are invoked during chunk building and that thus should use this method include, but are
* not limited to, {@code FabricBakedModel#emitBlockQuads} (block models),
* {@code BlockColorProvider#getColor} (block color providers), and
* {@code FabricBlock#getAppearance} (block appearance computation).
*
* <p>Users of this method are required to check the returned object before using it. Users must
* check if it is null and if it is of the correct type to avoid null pointer and class cast
* exceptions, as the returned data is not guaranteed to be what the user expects. A simple way
* to implement these checks is to use {@code instanceof}, since it always returns {@code false}
* if the object is null. If the {@code instanceof} returns {@code false}, a fallback path should
* be used.
*
* @param pos the position of the block entity
* @return the render data provided by the block entity, or null if there is no block entity at this position
*
* @see RenderDataBlockEntity
*/
@Nullable
default Object getBlockEntityRenderData(BlockPos pos) {
BlockEntity blockEntity = ((BlockView) this).getBlockEntity(pos);
return blockEntity == null ? null : blockEntity.getRenderData();
}
/**
* Checks whether biome retrieval is supported. The returned value will not change between
* multiple calls of this method. See {@link #getBiomeFabric(BlockPos)} for more information.
*
* @return whether biome retrieval is supported
* @see #getBiomeFabric(BlockPos)
*/
default boolean hasBiomes() {
return false;
}
/**
* Retrieves the biome at the given position if biome retrieval is supported. If
* {@link #hasBiomes()} returns {@code true}, this method will always return a non-null
* {@link RegistryEntry} whose {@link RegistryEntry#value() value} is non-null. If
* {@link #hasBiomes()} returns {@code false}, this method will always return {@code null}.
*
* <p>Prefer using {@link WorldView#getBiome(BlockPos)} instead of this method if this instance
* is known to implement {@link WorldView}.
*
* @implNote Implementations which do not return null are encouraged to use the plains biome as
* the default value, for example when the biome at the given position is unknown.
*
* @param pos the position for which to retrieve the biome
* @return the biome, or null if biome retrieval is not supported
* @see #hasBiomes()
*/
@UnknownNullability
default RegistryEntry<Biome> getBiomeFabric(BlockPos pos) {
return null;
}
}

View file

@ -0,0 +1,63 @@
/*
* 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.blockview.v2;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
/**
* Extensions that allow {@link BlockEntity} subclasses to provide render data.
*
* <p>Block entity render data is arbitrary data that captures some useful state of the
* {@link BlockEntity} and is safe to use in a multithreaded environment. In these environments,
* accessing and using a {@link BlockEntity} directly via {@link BlockView#getBlockEntity(BlockPos)}
* may not be thread-safe since the {@link BlockEntity} may be modified on a different thread, and it
* may not be consistent since accessing the internal state of the {@link BlockEntity} could modify it
* in a non-atomic way (such as through lazy computation). Using render data avoids these issues.
*
* <h3>Implementation Tips</h3>
*
* <p>The simplest form of render data is a value or object that is immutable. If only one such value
* must serve as render data, then it can be returned directly. An example of this would be returning
* an {@code Integer} that represents some internal state of a block entity. If more than one value
* must be used as render data, it can be packaged into an object that cannot be modified externally,
* such as a record. It is also possible to make render data a mutable object, but it must be ensured
* that changes to the internal state of this object are atomic and safe.
*
* <p>Note: This interface is automatically implemented on all {@link BlockEntity} instances via Mixin and interface injection.
*/
public interface RenderDataBlockEntity {
/**
* Gets the render data provided by this block entity. The returned object must be safe to
* use in a multithreaded environment.
*
* <p>Note: <b>This method should not be called directly</b>; use
* {@link FabricBlockView#getBlockEntityRenderData(BlockPos)} instead. Only call this
* method when the result is used to implement
* {@link FabricBlockView#getBlockEntityRenderData(BlockPos)}.
*
* @return the render data
* @see FabricBlockView#getBlockEntityRenderData(BlockPos)
*/
@Nullable
default Object getRenderData() {
return null;
}
}

View file

@ -14,18 +14,14 @@
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.rendering.data.attachment;
package net.fabricmc.fabric.mixin.blockview;
import org.spongepowered.asm.mixin.Mixin;
import net.minecraft.block.entity.BlockEntity;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity;
import net.fabricmc.fabric.api.blockview.v2.RenderDataBlockEntity;
@Mixin(BlockEntity.class)
public class BlockEntityMixin implements RenderAttachmentBlockEntity {
@Override
public Object getRenderAttachmentData() {
return null;
}
public abstract class BlockEntityMixin implements RenderDataBlockEntity {
}

View file

@ -0,0 +1,27 @@
/*
* 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.blockview;
import org.spongepowered.asm.mixin.Mixin;
import net.minecraft.world.BlockView;
import net.fabricmc.fabric.api.blockview.v2.FabricBlockView;
@Mixin(BlockView.class)
public interface BlockViewMixin extends FabricBlockView {
}

View file

@ -0,0 +1,42 @@
/*
* 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.blockview;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.WorldView;
import net.minecraft.world.biome.Biome;
@Mixin(WorldView.class)
public interface WorldViewMixin extends BlockRenderView {
@Shadow
RegistryEntry<Biome> getBiome(BlockPos pos);
@Override
default boolean hasBiomes() {
return true;
}
@Override
default RegistryEntry<Biome> getBiomeFabric(BlockPos pos) {
return getBiome(pos);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,12 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.blockview",
"compatibilityLevel": "JAVA_17",
"mixins": [
"BlockEntityMixin",
"BlockViewMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,37 @@
{
"schemaVersion": 1,
"id": "fabric-block-view-api-v2",
"name": "Fabric BlockView API (v2)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-block-view-api-v2/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"depends": {
"fabricloader": ">=0.14.21"
},
"description": "Hooks for block views",
"mixins": [
"fabric-block-view-api-v2.mixins.json",
{
"config": "fabric-block-view-api-v2.client.mixins.json",
"environment": "client"
}
],
"custom": {
"fabric-api:module-lifecycle": "stable",
"loom:injected_interfaces": {
"net/minecraft/class_1922": ["net/fabricmc/fabric/api/blockview/v2/FabricBlockView"],
"net/minecraft/class_2586": ["net/fabricmc/fabric/api/blockview/v2/RenderDataBlockEntity"]
}
},
"accessWidener": "fabric-block-view-api-v2.accesswidener"
}

View file

@ -4,10 +4,10 @@ moduleDependencies(project, ['fabric-api-base'])
testDependencies(project, [
':fabric-block-api-v1',
':fabric-block-view-api-v2',
':fabric-blockrenderlayer-v1',
':fabric-model-loading-api-v1',
':fabric-object-builder-api-v1',
':fabric-renderer-indigo',
':fabric-rendering-data-attachment-v1',
':fabric-resource-loader-v0'
])

View file

@ -34,7 +34,7 @@ import net.minecraft.world.BlockRenderView;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.block.v1.FabricBlock;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
import net.fabricmc.fabric.api.blockview.v2.FabricBlockView;
// Need to implement FabricBlock manually because this is a testmod for another Fabric module, otherwise it would be injected.
public class FrameBlock extends Block implements BlockEntityProvider, FabricBlock {
@ -101,8 +101,8 @@ public class FrameBlock extends Block implements BlockEntityProvider, FabricBloc
// but the goal here is just to test the behavior with the pillar's connected textures. ;-)
@Override
public BlockState getAppearance(BlockState state, BlockRenderView renderView, BlockPos pos, Direction side, @Nullable BlockState sourceState, @Nullable BlockPos sourcePos) {
// For this specific block, the render attachment works on both the client and the server, so let's use that.
if (((RenderAttachedBlockView) renderView).getBlockEntityRenderAttachment(pos) instanceof Block mimickedBlock) {
// For this specific block, the render data works on both the client and the server, so let's use that.
if (((FabricBlockView) renderView).getBlockEntityRenderData(pos) instanceof Block mimickedBlock) {
return mimickedBlock.getDefaultState();
}

View file

@ -29,9 +29,9 @@ import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity;
import net.fabricmc.fabric.api.blockview.v2.RenderDataBlockEntity;
public class FrameBlockEntity extends BlockEntity implements RenderAttachmentBlockEntity {
public class FrameBlockEntity extends BlockEntity implements RenderDataBlockEntity {
@Nullable
private Block block = null;
@ -86,7 +86,7 @@ public class FrameBlockEntity extends BlockEntity implements RenderAttachmentBlo
@Nullable
@Override
public Block getRenderAttachmentData() {
public Block getRenderData() {
return this.block;
}

View file

@ -33,6 +33,7 @@ public final class Registration {
public static final FrameBlock FRAME_VARIANT_BLOCK = register("frame_variant", new FrameBlock(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).nonOpaque()));
public static final Block PILLAR_BLOCK = register("pillar", new Block(FabricBlockSettings.create()));
public static final Block OCTAGONAL_COLUMN_BLOCK = register("octagonal_column", new Block(FabricBlockSettings.create().nonOpaque().strength(1.8F)));
public static final Block RIVERSTONE_BLOCK = register("riverstone", new Block(FabricBlockSettings.copyOf(Blocks.STONE)));
public static final FrameBlock[] FRAME_BLOCKS = new FrameBlock[] {
FRAME_BLOCK,
@ -45,6 +46,7 @@ public final class Registration {
public static final Item FRAME_VARIANT_ITEM = register("frame_variant", new BlockItem(FRAME_VARIANT_BLOCK, new Item.Settings()));
public static final Item PILLAR_ITEM = register("pillar", new BlockItem(PILLAR_BLOCK, new Item.Settings()));
public static final Item OCTAGONAL_COLUMN_ITEM = register("octagonal_column", new BlockItem(OCTAGONAL_COLUMN_BLOCK, new Item.Settings()));
public static final Item RIVERSTONE_ITEM = register("riverstone", new BlockItem(RIVERSTONE_BLOCK, new Item.Settings()));
public static final BlockEntityType<FrameBlockEntity> FRAME_BLOCK_ENTITY_TYPE = register("frame", FabricBlockEntityTypeBuilder.create(FrameBlockEntity::new, FRAME_BLOCKS).build(null));

View file

@ -37,6 +37,9 @@ import net.fabricmc.api.ModInitializer;
* <li>Octagonal columns have irregular faces to test enhanced AO and normal shade. The
* octagonal item column has glint force enabled on all faces except the top and bottom
* faces.
*
* <li>Riverstone blocks look like stone normally, but turn to gold in river biomes
* (biomes tagged with #minecraft:is_river).
* </ul>
*/
public final class RendererTest implements ModInitializer {

View file

@ -37,6 +37,7 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.fabric.api.blockview.v2.FabricBlockView;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
@ -44,7 +45,6 @@ import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
public class FrameBakedModel implements BakedModel {
private final Mesh frameMesh;
@ -72,17 +72,12 @@ public class FrameBakedModel implements BakedModel {
// Emit our frame mesh
this.frameMesh.outputTo(context.getEmitter());
RenderAttachedBlockView renderAttachedBlockView = (RenderAttachedBlockView) blockView;
// We cannot access the block entity from here. We should instead use the immutable render attachments provided by the block entity.
@Nullable
Block data = (Block) renderAttachedBlockView.getBlockEntityRenderAttachment(pos);
if (data == null) {
return; // No inner block to render
// We should not access the block entity from here. We should instead use the immutable render data provided by the block entity.
if (!(((FabricBlockView) blockView).getBlockEntityRenderData(pos) instanceof Block mimickedBlock)) {
return; // No inner block to render, or data of wrong type
}
BlockState innerState = data.getDefaultState();
BlockState innerState = mimickedBlock.getDefaultState();
// Now, we emit a transparent scaled-down version of the inner model
// Try both emissive and non-emissive versions of the translucent material

View file

@ -44,6 +44,11 @@ public class ModelResolverImpl implements ModelResolver {
RendererTest.id("item/octagonal_column")
);
private static final Set<Identifier> RIVERSTONE_MODEL_LOCATIONS = Set.of(
RendererTest.id("block/riverstone"),
RendererTest.id("item/riverstone")
);
@Override
@Nullable
public UnbakedModel resolveModel(Context context) {
@ -61,6 +66,10 @@ public class ModelResolverImpl implements ModelResolver {
return new OctagonalColumnUnbakedModel();
}
if (RIVERSTONE_MODEL_LOCATIONS.contains(id)) {
return new RiverstoneUnbakedModel();
}
return null;
}
}

View file

@ -0,0 +1,109 @@
/*
* 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.test.renderer.client;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.json.ModelOverrideList;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.texture.Sprite;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.tag.BiomeTags;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.fabric.api.blockview.v2.FabricBlockView;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
public class RiverstoneBakedModel implements BakedModel {
private final BakedModel regularModel;
private final BakedModel riverModel;
public RiverstoneBakedModel(BakedModel regularModel, BakedModel riverModel) {
this.regularModel = regularModel;
this.riverModel = riverModel;
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
if (((FabricBlockView) blockView).hasBiomes() && ((FabricBlockView) blockView).getBiomeFabric(pos).isIn(BiomeTags.IS_RIVER)) {
riverModel.emitBlockQuads(blockView, state, pos, randomSupplier, context);
} else {
regularModel.emitBlockQuads(blockView, state, pos, randomSupplier, context);
}
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
regularModel.emitItemQuads(stack, randomSupplier, context);
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) {
return Collections.emptyList();
}
@Override
public boolean useAmbientOcclusion() {
return true;
}
@Override
public boolean hasDepth() {
return false;
}
@Override
public boolean isSideLit() {
return true;
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public Sprite getParticleSprite() {
return regularModel.getParticleSprite();
}
@Override
public ModelTransformation getTransformation() {
return ModelHelper.MODEL_TRANSFORM_BLOCK;
}
@Override
public ModelOverrideList getOverrides() {
return ModelOverrideList.EMPTY;
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.test.renderer.client;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
public class RiverstoneUnbakedModel implements UnbakedModel {
private static final Identifier STONE_MODEL_ID = new Identifier("block/stone");
private static final Identifier GOLD_BLOCK_MODEL_ID = new Identifier("block/gold_block");
@Override
public Collection<Identifier> getModelDependencies() {
return Collections.emptySet();
}
@Override
public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
}
@Nullable
@Override
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
BakedModel stoneModel = baker.bake(STONE_MODEL_ID, rotationContainer);
BakedModel goldBlockModel = baker.bake(GOLD_BLOCK_MODEL_ID, rotationContainer);
return new RiverstoneBakedModel(stoneModel, goldBlockModel);
}
}

View file

@ -0,0 +1,5 @@
{
"variants": {
"": { "model": "fabric-renderer-api-v1-testmod:block/riverstone" }
}
}

View file

@ -1,7 +0,0 @@
version = getSubprojectVersion(project)
moduleDependencies(project, ['fabric-api-base'])
loom {
accessWidenerPath = file("src/main/resources/fabric-rendering-data-attachment-v1.accesswidener")
}

View file

@ -1,42 +0,0 @@
/*
* 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.attachment.client;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.spongepowered.asm.mixin.Mixin;
import net.minecraft.client.render.chunk.ChunkRendererRegion;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
import net.fabricmc.fabric.impl.rendering.data.attachment.RenderDataObjectConsumer;
@Mixin(ChunkRendererRegion.class)
public abstract class ChunkRendererRegionMixin implements RenderAttachedBlockView, RenderDataObjectConsumer {
private Long2ObjectOpenHashMap<Object> fabric_renderDataObjects;
@Override
public Object getBlockEntityRenderAttachment(BlockPos pos) {
return fabric_renderDataObjects == null ? null : fabric_renderDataObjects.get(pos.asLong());
}
// Called in MixinChunkRendererRegionBuilder
@Override
public void fabric_acceptRenderDataObjects(Long2ObjectOpenHashMap<Object> renderDataObjects) {
this.fabric_renderDataObjects = renderDataObjects;
}
}

View file

@ -1,70 +0,0 @@
/*
* 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.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.World;
/**
* {@link BlockRenderView}-extending interface to be used by {@code FabricBakedModel}
* for dynamic model customization. It ensures thread safety and exploits data cached in render
* chunks for performance and data consistency. This interface is guaranteed to be implemented on
* every {@link BlockRenderView} subclass, and as such any {@link BlockRenderView}
* can be safely cast to {@link RenderAttachedBlockView}.
*
* <p>There are differences from regular {@link World} access that consumers must understand:
*
* <p>BlockEntity implementations that provide data for model customization should implement
* {@link RenderAttachmentBlockEntity} which will be queried on the main thread when a render
* chunk is enqueued for rebuild. The model should retrieve the results by casting the
* {@link BlockRenderView} to this class and then calling {@link #getBlockEntityRenderAttachment(BlockPos)}.
* While {@link #getBlockEntity(net.minecraft.util.math.BlockPos)} is not disabled, it
* is not thread-safe for use on render threads. Models that violate this guidance are
* responsible for any necessary synchronization or collision detection.
*
* <p>{@link #getBlockState(net.minecraft.util.math.BlockPos)} and {@link #getFluidState(net.minecraft.util.math.BlockPos)}
* will always reflect the state cached with the render chunk. Block and fluid states
* can thus be different from main-thread world state due to lag between block update
* application from network packets and render chunk rebuilds. Use of {link #getCachedRenderData()}
* will ensure consistency of model state with the rest of the chunk being rendered.
*
* <p>Models should avoid using {@link BlockRenderView#getBlockEntity(BlockPos)}
* to ensure thread safety because this view may be accessed outside the main client thread.
* Models that require Block Entity data should implement {@link RenderAttachmentBlockEntity}
* on their block entity class, cast the {@link BlockRenderView} to {@link RenderAttachedBlockView}
* and then use {@link #getBlockEntityRenderAttachment(BlockPos)} to retrieve the data. When called from the
* main thread, that method will simply retrieve the data directly.
*/
public interface RenderAttachedBlockView extends BlockRenderView {
/**
* For models associated with Block Entities that implement {@link RenderAttachmentBlockEntity}
* this will be the most recent value provided by that implementation for the given block position.
*
* <p>Null in all other cases, or if the result from the implementation was null.
*
* @param pos Position of the block for the block model.
*/
@Nullable
default Object getBlockEntityRenderAttachment(BlockPos pos) {
BlockEntity be = this.getBlockEntity(pos);
return be == null ? null : ((RenderAttachmentBlockEntity) be).getRenderAttachmentData();
}
}

View file

@ -1,46 +0,0 @@
/*
* 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.minecraft.world.BlockRenderView;
/**
* Interface for {@link BlockEntity}s which provide dynamic model state data.
*
* <p>Dynamic model state data is separate from BlockState, and will be
* cached during render chunk building on the main thread (safely) and accessible
* during chunk rendering on non-main threads.
*
* <p>To access the dynamic data, cast the {@link BlockRenderView} to {@link RenderAttachedBlockView},
* and then call {@link #getRenderAttachmentData()} with the correct position.
*
* <p>Due to chunk meshing happening on non-main threads, please ensure that all accesses to the passed model data are
* thread-safe. This can be achieved by, for example, passing a pre-generated
* immutable object, or ensuring all gets performed on the passed object are atomic
* and well-checked for unusual states.
*/
@FunctionalInterface
public interface RenderAttachmentBlockEntity {
/**
* @return The model state data provided by this block entity. Can be null.
*/
@Nullable
Object getRenderAttachmentData();
}

View file

@ -16,6 +16,7 @@ fabric-api-base-version=0.4.32
fabric-api-lookup-api-v1-version=1.6.38
fabric-biome-api-v1-version=13.0.11
fabric-block-api-v1-version=1.0.10
fabric-block-view-api-v2-version=1.0.0
fabric-blockrenderlayer-v1-version=1.1.42
fabric-command-api-v1-version=1.2.35
fabric-command-api-v2-version=2.2.14

View file

@ -16,9 +16,12 @@ include 'fabric-api-base'
include 'fabric-api-lookup-api-v1'
include 'fabric-biome-api-v1'
include 'fabric-block-api-v1'
include 'fabric-block-view-api-v2'
include 'fabric-blockrenderlayer-v1'
include 'fabric-client-tags-api-v1'
include 'fabric-command-api-v2'
include 'fabric-content-registries-v0'
include 'fabric-convention-tags-v1'
include 'fabric-crash-report-info-v1'
include 'fabric-data-generation-api-v1'
include 'fabric-dimensions-v1'
@ -41,26 +44,23 @@ include 'fabric-recipe-api-v1'
include 'fabric-registry-sync-v0'
include 'fabric-renderer-api-v1'
include 'fabric-renderer-indigo'
include 'fabric-rendering-v1'
include 'fabric-rendering-data-attachment-v1'
include 'fabric-rendering-fluids-v1'
include 'fabric-rendering-v1'
include 'fabric-resource-conditions-api-v1'
include 'fabric-resource-loader-v0'
include 'fabric-screen-api-v1'
include 'fabric-screen-handler-api-v1'
include 'fabric-sound-api-v1'
include 'fabric-transfer-api-v1'
include 'fabric-convention-tags-v1'
include 'fabric-client-tags-api-v1'
include 'fabric-transitive-access-wideners-v1'
include 'deprecated'
include 'deprecated:fabric-commands-v0'
include 'deprecated:fabric-command-api-v1'
include 'deprecated:fabric-commands-v0'
include 'deprecated:fabric-containers-v0'
include 'deprecated:fabric-events-lifecycle-v0'
include 'deprecated:fabric-keybindings-v0'
include 'deprecated:fabric-models-v0'
include 'deprecated:fabric-renderer-registries-v1'
include 'deprecated:fabric-rendering-data-attachment-v1'
include 'deprecated:fabric-rendering-v0'