From 6a60afdd2e2287bb560fbcdb33193fde9e82cad6 Mon Sep 17 00:00:00 2001 From: Jochen Jacobs Date: Fri, 9 Feb 2024 14:05:03 +0000 Subject: [PATCH] Extend Data Attachment API to ProtoChunk (#3548) * allow data-attachment on ProtoChunks - moved interfaceInjection from WorldChunk to Chunk - dataAttachment saving on ProtoChunks in ChunkSerializer - copy attachment from ProtoChunk to WorldChunk on creation. - make WrapperProtoChunk wrap attachment calls to WorldChunk * add test for data-attachment on ProtoChunks, and extend testmod. * code style and license headers * fix typos in javadoc * extend testmod to test setting attachment during worldgen. * code formatting * fix testmod: don't crash when feature isn't placed (i.e. on GameTest server) * add warning when adding persistent attachment to chunk with status EMPTY. * update javadoc * update javadoc to reference ServerLivingEntityEvents#MOB_CONVERSION (cherry picked from commit 32782cfdc76e32bf7eebab96f2e32e83f640f0b7) --- fabric-data-attachment-api-v1/build.gradle | 3 +- .../client/ClientPlayNetworkHandlerMixin.java | 2 +- .../api/attachment/v1/AttachmentTarget.java | 12 ++- .../api/attachment/v1/AttachmentType.java | 23 +++--- .../impl/attachment/AttachmentEntrypoint.java | 11 ++- .../impl/attachment/AttachmentTargetImpl.java | 9 ++- .../attachment/AttachmentTargetsMixin.java | 11 ++- .../attachment/ChunkSerializerMixin.java | 20 +++-- .../mixin/attachment/WorldChunkMixin.java | 40 ++++++++++ .../attachment/WrapperProtoChunkMixin.java | 75 ++++++++++++++++++ .../fabric-data-attachment-api-v1.mixins.json | 4 +- .../src/main/resources/fabric.mod.json | 2 +- .../attachment/CommonAttachmentTests.java | 8 +- .../test/attachment/AttachmentTestMod.java | 77 ++++++++++++++++--- .../test/attachment/SetAttachmentFeature.java | 50 ++++++++++++ .../placed_feature/set_attachment.json | 7 ++ .../src/testmod/resources/fabric.mod.json | 3 +- 17 files changed, 313 insertions(+), 44 deletions(-) create mode 100644 fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/WorldChunkMixin.java create mode 100644 fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/WrapperProtoChunkMixin.java create mode 100644 fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/SetAttachmentFeature.java create mode 100644 fabric-data-attachment-api-v1/src/testmod/resources/data/fabric-data-attachment-api-v1-testmod/worldgen/placed_feature/set_attachment.json diff --git a/fabric-data-attachment-api-v1/build.gradle b/fabric-data-attachment-api-v1/build.gradle index 10d208c1f..cd51ebaf9 100644 --- a/fabric-data-attachment-api-v1/build.gradle +++ b/fabric-data-attachment-api-v1/build.gradle @@ -7,5 +7,6 @@ moduleDependencies(project, [ ]) testDependencies(project, [ - ':fabric-lifecycle-events-v1' + ':fabric-lifecycle-events-v1', + ':fabric-biome-api-v1' ]) diff --git a/fabric-data-attachment-api-v1/src/client/java/net/fabricmc/fabric/mixin/attachment/client/ClientPlayNetworkHandlerMixin.java b/fabric-data-attachment-api-v1/src/client/java/net/fabricmc/fabric/mixin/attachment/client/ClientPlayNetworkHandlerMixin.java index 093082c14..c26be0d04 100644 --- a/fabric-data-attachment-api-v1/src/client/java/net/fabricmc/fabric/mixin/attachment/client/ClientPlayNetworkHandlerMixin.java +++ b/fabric-data-attachment-api-v1/src/client/java/net/fabricmc/fabric/mixin/attachment/client/ClientPlayNetworkHandlerMixin.java @@ -38,7 +38,7 @@ abstract class ClientPlayNetworkHandlerMixin { /* * The KEEP_ATTRIBUTES flag is not set on a death respawn, and set in all other cases */ - AttachmentTargetImpl.copyOnRespawn(oldPlayer, newPlayer, !packet.hasFlag(PlayerRespawnS2CPacket.KEEP_ATTRIBUTES)); + AttachmentTargetImpl.transfer(oldPlayer, newPlayer, !packet.hasFlag(PlayerRespawnS2CPacket.KEEP_ATTRIBUTES)); init.call(newPlayer); } } diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentTarget.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentTarget.java index 3cdf07dfc..77dda86d7 100644 --- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentTarget.java +++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentTarget.java @@ -28,14 +28,14 @@ import net.minecraft.block.entity.BlockEntity; import net.minecraft.entity.Entity; import net.minecraft.server.world.ServerWorld; import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.WorldChunk; +import net.minecraft.world.chunk.ChunkStatus; /** * Marks all objects on which data can be attached using {@link AttachmentType}s. * - *

Fabric implements this on {@link Entity}, {@link BlockEntity}, {@link ServerWorld} and {@link WorldChunk} via mixin.

+ *

Fabric implements this on {@link Entity}, {@link BlockEntity}, {@link ServerWorld} and {@link Chunk} via mixin.

* - *

Note about {@link BlockEntity} and {@link WorldChunk} targets: these objects need to be notified of changes to their + *

Note about {@link BlockEntity} and {@link Chunk} targets: these objects need to be notified of changes to their * state (using {@link BlockEntity#markDirty()} and {@link Chunk#setNeedsSaving(boolean)} respectively), otherwise the modifications will not take effect properly. * The {@link #setAttached(AttachmentType, Object)} method handles this automatically, but this needs to be done manually * when attached data is mutable, for example: @@ -55,6 +55,12 @@ import net.minecraft.world.chunk.WorldChunk; * which takes care of all vanilla types. However, modded block entities may be coded differently, so be wary of this * when attaching data to modded block entities. *

+ * + *

+ * Note about {@link Chunk} targets with {@link ChunkStatus#EMPTY}: These chunks are not saved unless the generation + * progresses to at least {@link ChunkStatus#STRUCTURE_STARTS}. Therefore, persistent attachments to those chunks may not + * be saved. The {@link #setAttached(AttachmentType, Object)} method will log a warning when this is attempted. + *

*/ @ApiStatus.Experimental @ApiStatus.NonExtendable diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentType.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentType.java index e8d07e028..cfae90949 100644 --- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentType.java +++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/api/attachment/v1/AttachmentType.java @@ -23,11 +23,14 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.mob.MobEntity; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Identifier; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ProtoChunk; +import net.minecraft.world.chunk.WorldChunk; import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents; +import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; /** @@ -39,13 +42,15 @@ import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; * immutable types. More generally, different attachments must not share mutable state, and it is strongly advised * for attachments not to hold internal references to their target. See the following note on entity targets.

* - *

Note on {@link Entity} targets: in several instances, the name needs to copy data from one {@link Entity} to another. - * These are player respawning, mob conversion, return from the End and cross-world entity teleportation. By default, - * attachments are simply copied wholesale, up to {@link #copyOnDeath()}. Since one entity instance is discarded, - * an attachment that keeps a reference to an {@link Entity} instance can and will break unexpectedly. If, - * for whatever reason, keeping to reference to the target entity is absolutely necessary, be sure to use - * {@link ServerPlayerEvents#COPY_FROM}, {@link ServerEntityWorldChangeEvents#AFTER_ENTITY_CHANGE_WORLD} - * and a mixin into {@link MobEntity#convertTo(EntityType, boolean)} to implement custom copying logic.

+ *

Note on {@link Entity} and {@link Chunk} targets: in several instances, the game needs to copy data from one instance to another. + * These are player respawning, mob conversion, return from the End, cross-world entity teleportation, and conversion of a {@link ProtoChunk} to + * {@link WorldChunk}. By default, attachments are simply copied wholesale, up to {@link #copyOnDeath()}. Since one instance is discarded, + * an attachment that keeps a reference to an {@link Entity} or {@link ProtoChunk} instance can and will break unexpectedly. If, + * for whatever reason, keeping a reference to the target is absolutely necessary, be sure to implement custom copying logic. + * For {@link Entity} targets, use {@link ServerPlayerEvents#COPY_FROM}, {@link ServerEntityWorldChangeEvents#AFTER_ENTITY_CHANGE_WORLD}, + * and {@link ServerLivingEntityEvents#MOB_CONVERSION}. For {@link Chunk} targets, mixin into + * {@link WorldChunk#WorldChunk(ServerWorld, ProtoChunk, WorldChunk.EntityLoader)}. + *

* * @param type of the attached data. It is encouraged for this to be an immutable type. */ diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentEntrypoint.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentEntrypoint.java index 92e848d6a..dc5fa5810 100644 --- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentEntrypoint.java +++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentEntrypoint.java @@ -16,23 +16,28 @@ package net.fabricmc.fabric.impl.attachment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents; import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; public class AttachmentEntrypoint implements ModInitializer { + public static final Logger LOGGER = LoggerFactory.getLogger("fabric-data-attachment-api-v1"); + @Override public void onInitialize() { ServerPlayerEvents.COPY_FROM.register((oldPlayer, newPlayer, alive) -> - AttachmentTargetImpl.copyOnRespawn(oldPlayer, newPlayer, !alive) + AttachmentTargetImpl.transfer(oldPlayer, newPlayer, !alive) ); ServerEntityWorldChangeEvents.AFTER_ENTITY_CHANGE_WORLD.register(((originalEntity, newEntity, origin, destination) -> - AttachmentTargetImpl.copyOnRespawn(originalEntity, newEntity, false)) + AttachmentTargetImpl.transfer(originalEntity, newEntity, false)) ); // using the corresponding player event is unnecessary as no new instance is created ServerLivingEntityEvents.MOB_CONVERSION.register((previous, converted, keepEquipment) -> - AttachmentTargetImpl.copyOnRespawn(previous, converted, true) + AttachmentTargetImpl.transfer(previous, converted, true) ); } } diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentTargetImpl.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentTargetImpl.java index c10d17f33..6cf81b0be 100644 --- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentTargetImpl.java +++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/impl/attachment/AttachmentTargetImpl.java @@ -28,12 +28,13 @@ import net.fabricmc.fabric.api.attachment.v1.AttachmentType; public interface AttachmentTargetImpl extends AttachmentTarget { /** - * Copies entity attachments when it is respawned and a new instance is created. - * Is triggered on player respawn, entity conversion, return from the End or cross-world entity teleportation. + * Copies attachments from the original to the target. This is used when a ProtoChunk is converted to a + * WorldChunk, and when an entity is respawned and a new instance is created. For entity respawns, it is + * triggered on player respawn, entity conversion, return from the End, or cross-world entity teleportation. * In the first two cases, only the attachments with {@link AttachmentType#copyOnDeath()} will be transferred. - */ + */ @SuppressWarnings("unchecked") - static void copyOnRespawn(AttachmentTarget original, AttachmentTarget target, boolean isDeath) { + static void transfer(AttachmentTarget original, AttachmentTarget target, boolean isDeath) { Map, ?> attachments = ((AttachmentTargetImpl) original).fabric_getAttachments(); if (attachments == null) { diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/AttachmentTargetsMixin.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/AttachmentTargetsMixin.java index bd1bef57c..22acd8554 100644 --- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/AttachmentTargetsMixin.java +++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/AttachmentTargetsMixin.java @@ -28,13 +28,14 @@ import net.minecraft.nbt.NbtCompound; import net.minecraft.registry.RegistryWrapper; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.WorldChunk; +import net.minecraft.world.chunk.ChunkStatus; import net.fabricmc.fabric.api.attachment.v1.AttachmentType; +import net.fabricmc.fabric.impl.attachment.AttachmentEntrypoint; import net.fabricmc.fabric.impl.attachment.AttachmentSerializingImpl; import net.fabricmc.fabric.impl.attachment.AttachmentTargetImpl; -@Mixin({BlockEntity.class, Entity.class, World.class, WorldChunk.class}) +@Mixin({BlockEntity.class, Entity.class, World.class, Chunk.class}) abstract class AttachmentTargetsMixin implements AttachmentTargetImpl { @Nullable private IdentityHashMap, Object> fabric_dataAttachments = null; @@ -55,8 +56,12 @@ abstract class AttachmentTargetsMixin implements AttachmentTargetImpl { if (thisObject instanceof BlockEntity) { ((BlockEntity) thisObject).markDirty(); - } else if (thisObject instanceof WorldChunk) { + } else if (thisObject instanceof Chunk) { ((Chunk) thisObject).setNeedsSaving(true); + + if (type.isPersistent() && ((Chunk) thisObject).getStatus().equals(ChunkStatus.EMPTY)) { + AttachmentEntrypoint.LOGGER.warn("Attaching persistent attachment {} to chunk with chunk status EMPTY. Attachment might be discarded.", type.identifier()); + } } if (value == null) { diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ChunkSerializerMixin.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ChunkSerializerMixin.java index a06d8aed5..665936888 100644 --- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ChunkSerializerMixin.java +++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ChunkSerializerMixin.java @@ -27,7 +27,7 @@ import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.ChunkSerializer; import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.ProtoChunk; import net.minecraft.world.chunk.WorldChunk; import net.minecraft.world.poi.PointOfInterestStorage; @@ -42,18 +42,28 @@ abstract class ChunkSerializerMixin { ), method = "deserialize" ) - private static WorldChunk readChunkAttachments(WorldChunk chunk, ServerWorld world, PointOfInterestStorage poiStorage, ChunkPos chunkPos, NbtCompound nbt) { + private static WorldChunk readWorldChunkAttachments(WorldChunk chunk, ServerWorld world, PointOfInterestStorage poiStorage, ChunkPos chunkPos, NbtCompound nbt) { ((AttachmentTargetImpl) chunk).fabric_readAttachmentsFromNbt(nbt, world.getRegistryManager()); return chunk; } + @ModifyExpressionValue( + at = @At( + value = "NEW", + target = "net/minecraft/world/chunk/ProtoChunk" + ), + method = "deserialize" + ) + private static ProtoChunk readProtoChunkAttachments(ProtoChunk chunk, ServerWorld world, PointOfInterestStorage poiStorage, ChunkPos chunkPos, NbtCompound nbt) { + ((AttachmentTargetImpl) chunk).fabric_readAttachmentsFromNbt(nbt); + return chunk; + } + @Inject( at = @At("RETURN"), method = "serialize" ) private static void writeChunkAttachments(ServerWorld world, Chunk chunk, CallbackInfoReturnable cir) { - if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.LEVELCHUNK) { - ((AttachmentTargetImpl) chunk).fabric_writeAttachmentsToNbt(cir.getReturnValue(), world.getRegistryManager()); - } + ((AttachmentTargetImpl) chunk).fabric_writeAttachmentsToNbt(cir.getReturnValue(), world.getRegistryManager()); } } diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/WorldChunkMixin.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/WorldChunkMixin.java new file mode 100644 index 000000000..f7c538698 --- /dev/null +++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/WorldChunkMixin.java @@ -0,0 +1,40 @@ +/* + * 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.attachment; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.chunk.ProtoChunk; +import net.minecraft.world.chunk.WorldChunk; + +import net.fabricmc.fabric.api.attachment.v1.AttachmentTarget; +import net.fabricmc.fabric.impl.attachment.AttachmentTargetImpl; + +@Mixin(WorldChunk.class) +public class WorldChunkMixin { + @Inject( + method = "(Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/world/chunk/ProtoChunk;Lnet/minecraft/world/chunk/WorldChunk$EntityLoader;)V", + at = @At("TAIL") + ) + public void transferProtoChunkAttachement(ServerWorld world, ProtoChunk protoChunk, WorldChunk.EntityLoader entityLoader, CallbackInfo ci) { + AttachmentTargetImpl.transfer(protoChunk, (AttachmentTarget) this, false); + } +} diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/WrapperProtoChunkMixin.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/WrapperProtoChunkMixin.java new file mode 100644 index 000000000..9d4fd3902 --- /dev/null +++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/WrapperProtoChunkMixin.java @@ -0,0 +1,75 @@ +/* + * 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.attachment; + +import java.util.Map; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.world.chunk.WorldChunk; +import net.minecraft.world.chunk.WrapperProtoChunk; + +import net.fabricmc.fabric.api.attachment.v1.AttachmentType; +import net.fabricmc.fabric.impl.attachment.AttachmentTargetImpl; + +@Mixin(WrapperProtoChunk.class) +public class WrapperProtoChunkMixin implements AttachmentTargetImpl { + @Shadow + @Final + private WorldChunk wrapped; + + @Override + @Nullable + public T getAttached(AttachmentType type) { + return this.wrapped.getAttached(type); + } + + @Override + @Nullable + public T setAttached(AttachmentType type, @Nullable T value) { + return this.wrapped.setAttached(type, value); + } + + @Override + public boolean hasAttached(AttachmentType type) { + return this.wrapped.hasAttached(type); + } + + @Override + public void fabric_writeAttachmentsToNbt(NbtCompound nbt) { + ((AttachmentTargetImpl) this.wrapped).fabric_writeAttachmentsToNbt(nbt); + } + + @Override + public void fabric_readAttachmentsFromNbt(NbtCompound nbt) { + ((AttachmentTargetImpl) this.wrapped).fabric_readAttachmentsFromNbt(nbt); + } + + @Override + public boolean fabric_hasPersistentAttachments() { + return ((AttachmentTargetImpl) this.wrapped).fabric_hasPersistentAttachments(); + } + + @Override + public Map, ?> fabric_getAttachments() { + return ((AttachmentTargetImpl) this.wrapped).fabric_getAttachments(); + } +} diff --git a/fabric-data-attachment-api-v1/src/main/resources/fabric-data-attachment-api-v1.mixins.json b/fabric-data-attachment-api-v1/src/main/resources/fabric-data-attachment-api-v1.mixins.json index 1ce1c066a..57e55caff 100644 --- a/fabric-data-attachment-api-v1/src/main/resources/fabric-data-attachment-api-v1.mixins.json +++ b/fabric-data-attachment-api-v1/src/main/resources/fabric-data-attachment-api-v1.mixins.json @@ -8,7 +8,9 @@ "BlockEntityUpdateS2CPacketMixin", "ChunkSerializerMixin", "EntityMixin", - "ServerWorldMixin" + "ServerWorldMixin", + "WorldChunkMixin", + "WrapperProtoChunkMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-data-attachment-api-v1/src/main/resources/fabric.mod.json b/fabric-data-attachment-api-v1/src/main/resources/fabric.mod.json index 9b6ae5f58..ee82f417c 100644 --- a/fabric-data-attachment-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-data-attachment-api-v1/src/main/resources/fabric.mod.json @@ -37,7 +37,7 @@ "fabric-api:module-lifecycle": "experimental", "loom:injected_interfaces": { "net/minecraft/class_2586": ["net/fabricmc/fabric/api/attachment/v1/AttachmentTarget"], - "net/minecraft/class_2818": ["net/fabricmc/fabric/api/attachment/v1/AttachmentTarget"], + "net/minecraft/class_2791": ["net/fabricmc/fabric/api/attachment/v1/AttachmentTarget"], "net/minecraft/class_1297": ["net/fabricmc/fabric/api/attachment/v1/AttachmentTarget"], "net/minecraft/class_3218": ["net/fabricmc/fabric/api/attachment/v1/AttachmentTarget"] } diff --git a/fabric-data-attachment-api-v1/src/test/java/net/fabricmc/fabric/test/attachment/CommonAttachmentTests.java b/fabric-data-attachment-api-v1/src/test/java/net/fabricmc/fabric/test/attachment/CommonAttachmentTests.java index 3c72ee3a2..5ba1a46f6 100644 --- a/fabric-data-attachment-api-v1/src/test/java/net/fabricmc/fabric/test/attachment/CommonAttachmentTests.java +++ b/fabric-data-attachment-api-v1/src/test/java/net/fabricmc/fabric/test/attachment/CommonAttachmentTests.java @@ -48,6 +48,7 @@ import net.minecraft.nbt.NbtElement; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.chunk.ProtoChunk; import net.minecraft.world.chunk.WorldChunk; import net.fabricmc.fabric.api.attachment.v1.AttachmentRegistry; @@ -82,8 +83,9 @@ public class CommonAttachmentTests { Entity entity = mock(Entity.class, CALLS_REAL_METHODS); BlockEntity blockEntity = mock(BlockEntity.class, CALLS_REAL_METHODS); WorldChunk worldChunk = mock(WorldChunk.class, CALLS_REAL_METHODS); + ProtoChunk protoChunk = mock(ProtoChunk.class, CALLS_REAL_METHODS); - for (AttachmentTarget target : new AttachmentTarget[]{serverWorld, entity, blockEntity, worldChunk}) { + for (AttachmentTarget target : new AttachmentTarget[]{serverWorld, entity, blockEntity, worldChunk, protoChunk}) { testForTarget(target, basic); } } @@ -161,8 +163,8 @@ public class CommonAttachmentTests { Entity respawnTarget = mock(Entity.class, CALLS_REAL_METHODS); Entity nonRespawnTarget = mock(Entity.class, CALLS_REAL_METHODS); - AttachmentTargetImpl.copyOnRespawn(original, respawnTarget, true); - AttachmentTargetImpl.copyOnRespawn(original, nonRespawnTarget, false); + AttachmentTargetImpl.transfer(original, respawnTarget, true); + AttachmentTargetImpl.transfer(original, nonRespawnTarget, false); assertTrue(respawnTarget.hasAttached(copiedOnRespawn)); assertFalse(respawnTarget.hasAttached(notCopiedOnRespawn)); assertTrue(nonRespawnTarget.hasAttached(copiedOnRespawn)); diff --git a/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/AttachmentTestMod.java b/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/AttachmentTestMod.java index 5c7e4dbbf..dad16a7e2 100644 --- a/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/AttachmentTestMod.java +++ b/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/AttachmentTestMod.java @@ -20,13 +20,27 @@ import com.mojang.serialization.Codec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Identifier; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.ProtoChunk; import net.minecraft.world.chunk.WorldChunk; +import net.minecraft.world.chunk.WrapperProtoChunk; +import net.minecraft.world.gen.GenerationStep; +import net.minecraft.world.gen.feature.DefaultFeatureConfig; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.attachment.v1.AttachmentRegistry; import net.fabricmc.fabric.api.attachment.v1.AttachmentType; +import net.fabricmc.fabric.api.biome.v1.BiomeModifications; +import net.fabricmc.fabric.api.biome.v1.BiomeSelectors; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; public class AttachmentTestMod implements ModInitializer { @@ -36,33 +50,78 @@ public class AttachmentTestMod implements ModInitializer { new Identifier(MOD_ID, "persistent"), Codec.STRING ); + public static final AttachmentType FEATURE_ATTACHMENT = AttachmentRegistry.create( + new Identifier(MOD_ID, "feature") + ); + + public static final ChunkPos FAR_CHUNK_POS = new ChunkPos(30, 0); private boolean firstLaunch = true; + public static boolean featurePlaced = false; @Override public void onInitialize() { + Registry.register(Registries.FEATURE, new Identifier(MOD_ID, "set_attachment"), new SetAttachmentFeature(DefaultFeatureConfig.CODEC)); + + BiomeModifications.addFeature( + BiomeSelectors.foundInOverworld(), + GenerationStep.Feature.VEGETAL_DECORATION, + RegistryKey.of(RegistryKeys.PLACED_FEATURE, new Identifier(MOD_ID, "set_attachment")) + ); + ServerLifecycleEvents.SERVER_STARTED.register(server -> { ServerWorld overworld; WorldChunk chunk; - if (firstLaunch) { - LOGGER.info("First launch, setting up"); + overworld = server.getOverworld(); + chunk = overworld.getChunk(0, 0); + + if (firstLaunch) { + LOGGER.info("First launch, testing attachment by feature"); + + if (featurePlaced) { + if (!"feature".equals(chunk.getAttached(FEATURE_ATTACHMENT))) { + throw new AssertionError("Feature did not write attachment to ProtoChunk"); + } + } else { + LOGGER.warn("Feature not placed, could not test writing during worldgen"); + } + + LOGGER.info("setting up persistent attachments"); - overworld = server.getOverworld(); overworld.setAttached(PERSISTENT, "world_data"); - chunk = overworld.getChunk(0, 0); chunk.setAttached(PERSISTENT, "chunk_data"); + + ProtoChunk protoChunk = (ProtoChunk) overworld.getChunkManager().getChunk(FAR_CHUNK_POS.x, FAR_CHUNK_POS.z, ChunkStatus.STRUCTURE_STARTS, true); + protoChunk.setAttached(PERSISTENT, "protochunk_data"); } else { - LOGGER.info("Second launch, testing"); + LOGGER.info("Second launch, testing persistent attachments"); - overworld = server.getOverworld(); - if (!"world_data".equals(overworld.getAttached(PERSISTENT))) throw new AssertionError(); + if (!"world_data".equals(overworld.getAttached(PERSISTENT))) throw new AssertionError("World attachement did not persist"); + if (!"chunk_data".equals(chunk.getAttached(PERSISTENT))) throw new AssertionError("WorldChunk attachement did not persist"); - chunk = overworld.getChunk(0, 0); - if (!"chunk_data".equals(chunk.getAttached(PERSISTENT))) throw new AssertionError(); + WrapperProtoChunk wrapperProtoChunk = (WrapperProtoChunk) overworld.getChunkManager().getChunk(0, 0, ChunkStatus.EMPTY, true); + if (!"chunk_data".equals(wrapperProtoChunk.getAttached(PERSISTENT))) throw new AssertionError("Attachement is not accessible through WrapperProtoChunk"); + + Chunk farChunk = overworld.getChunkManager().getChunk(FAR_CHUNK_POS.x, FAR_CHUNK_POS.z, ChunkStatus.EMPTY, true); + + if (farChunk instanceof WrapperProtoChunk) { + LOGGER.warn("Far chunk alread generated, can't test persistence in ProtoChunk."); + } else { + if (!"protochunk_data".equals(farChunk.getAttached(PERSISTENT))) throw new AssertionError("ProtoChunk attachement did not persist"); + } } }); ServerLifecycleEvents.SERVER_STOPPING.register(server -> firstLaunch = false); + + // Testing hint: load far chunk by running /tp @s 480 ~ 0 + ServerChunkEvents.CHUNK_LOAD.register(((world, chunk) -> { + if (!chunk.getPos().equals(FAR_CHUNK_POS)) return; + + LOGGER.info("Loaded chunk {}, testing transfer of attachments to WorldChunk", FAR_CHUNK_POS); + + if (!"protochunk_data".equals(chunk.getAttached(PERSISTENT))) throw new AssertionError("ProtoChunk attachement was not transfered to WorldChunk"); + })); } } diff --git a/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/SetAttachmentFeature.java b/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/SetAttachmentFeature.java new file mode 100644 index 000000000..a6a588df0 --- /dev/null +++ b/fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/SetAttachmentFeature.java @@ -0,0 +1,50 @@ +/* + * 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.attachment; + +import com.mojang.serialization.Codec; + +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ProtoChunk; +import net.minecraft.world.chunk.WrapperProtoChunk; +import net.minecraft.world.gen.feature.DefaultFeatureConfig; +import net.minecraft.world.gen.feature.Feature; +import net.minecraft.world.gen.feature.util.FeatureContext; + +public class SetAttachmentFeature extends Feature { + public SetAttachmentFeature(Codec codec) { + super(codec); + } + + @Override + public boolean generate(FeatureContext context) { + Chunk chunk = context.getWorld().getChunk(context.getOrigin()); + + if (chunk.getPos().equals(new ChunkPos(0, 0))) { + AttachmentTestMod.featurePlaced = true; + + if (!(chunk instanceof ProtoChunk) || chunk instanceof WrapperProtoChunk) { + AttachmentTestMod.LOGGER.warn("Feature not attaching to ProtoChunk"); + } + + chunk.setAttached(AttachmentTestMod.FEATURE_ATTACHMENT, "feature"); + } + + return true; + } +} diff --git a/fabric-data-attachment-api-v1/src/testmod/resources/data/fabric-data-attachment-api-v1-testmod/worldgen/placed_feature/set_attachment.json b/fabric-data-attachment-api-v1/src/testmod/resources/data/fabric-data-attachment-api-v1-testmod/worldgen/placed_feature/set_attachment.json new file mode 100644 index 000000000..538bf0731 --- /dev/null +++ b/fabric-data-attachment-api-v1/src/testmod/resources/data/fabric-data-attachment-api-v1-testmod/worldgen/placed_feature/set_attachment.json @@ -0,0 +1,7 @@ +{ + "feature": { + "type": "fabric-data-attachment-api-v1-testmod:set_attachment", + "config": {} + }, + "placement": [] +} diff --git a/fabric-data-attachment-api-v1/src/testmod/resources/fabric.mod.json b/fabric-data-attachment-api-v1/src/testmod/resources/fabric.mod.json index 9c369f4f8..28224b760 100644 --- a/fabric-data-attachment-api-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-data-attachment-api-v1/src/testmod/resources/fabric.mod.json @@ -7,7 +7,8 @@ "license": "Apache-2.0", "depends": { "fabric-data-attachment-api-v1": "*", - "fabric-lifecycle-events-v1": "*" + "fabric-lifecycle-events-v1": "*", + "fabric-biome-api-v1": "*" }, "entrypoints": { "main": [