Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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.impl.attachment;

import java.util.IdentityHashMap;
import java.util.Map;

import net.minecraft.world.level.storage.ValueInput;

import net.fabricmc.fabric.api.attachment.v1.AttachmentTarget;
import net.fabricmc.fabric.api.attachment.v1.AttachmentType;

/**
* Replacement logic to handle applying attachments when using the /data command.
* This applies the changes using the high level APIs, ensuring that the changes are correctly synced to the client.
*/
public class DataAccessorHandler {
public static ScopedValue<Void> APPLYING_DATA_CHANGE = ScopedValue.newInstance();

public static void applyDataChanges(AttachmentTarget target, ValueInput data, Runnable applyData) {
AttachmentTargetImpl targetImpl = (AttachmentTargetImpl) target;

Map<AttachmentType<?>, ?> oldAttachments = targetImpl.fabric_getAttachments();
ScopedValue.where(APPLYING_DATA_CHANGE, null).run(applyData);

if (oldAttachments != targetImpl.fabric_getAttachments()) {
throw new AssertionError("Attachment data changed during data change application.");
}

IdentityHashMap<AttachmentType<?>, Object> newAttachments = AttachmentSerializingImpl.deserializeAttachmentData(data);

if (oldAttachments == null && newAttachments == null) {
// No attachments before or after, nothing to do
return;
} else if (oldAttachments != null && (newAttachments == null || newAttachments.isEmpty())) {
// Clear all attachments - copy keys to avoid ConcurrentModificationException
oldAttachments.keySet().stream().toList().forEach(target::removeAttached);
return;
}

// Update the new attachments
newAttachments.forEach((attachmentType, o) -> target.setAttached((AttachmentType) attachmentType, o));

// Remove all of the removed attachments - copy keys to avoid ConcurrentModificationException
if (oldAttachments != null) {
oldAttachments.keySet().stream()
.filter(attachmentType -> !newAttachments.containsKey(attachmentType))
.toList()
.forEach(target::removeAttached);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import net.fabricmc.fabric.impl.attachment.AttachmentSerializingImpl;
import net.fabricmc.fabric.impl.attachment.AttachmentTargetImpl;
import net.fabricmc.fabric.impl.attachment.AttachmentTypeImpl;
import net.fabricmc.fabric.impl.attachment.DataAccessorHandler;
import net.fabricmc.fabric.impl.attachment.GlobalAttachmentsImpl;
import net.fabricmc.fabric.impl.attachment.sync.AttachmentChange;
import net.fabricmc.fabric.impl.attachment.sync.AttachmentSync;
Expand Down Expand Up @@ -137,6 +138,11 @@ public void fabric_writeAttachmentsToNbt(ValueOutput output) {

@Override
public void fabric_readAttachmentsFromNbt(ValueInput input) {
if (DataAccessorHandler.APPLYING_DATA_CHANGE.isBound()) {
// DataAccessorHandler handles applying data changes seperately.
return;
}

// Note on player targets: no syncing can happen here as the networkHandler is still null
// Instead it is done on player join (see AttachmentSync)
IdentityHashMap<AttachmentType<?>, Object> fromNbt = AttachmentSerializingImpl.deserializeAttachmentData(input);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.nbt.CompoundTag;
import net.minecraft.server.commands.data.BlockDataAccessor;
import net.minecraft.server.commands.data.DataAccessor;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.storage.TagValueInput;
import net.minecraft.world.level.storage.ValueInput;

import net.fabricmc.fabric.impl.attachment.DataAccessorHandler;

@Mixin(BlockDataAccessor.class)
public abstract class BlockDataAccessorMixin implements DataAccessor {
@Unique
private static final Logger LOGGER = LoggerFactory.getLogger("BlockDataAccessorMixin");

@Shadow
@Final
private BlockEntity entity;

@WrapMethod(method = "setData")
public void setData(CompoundTag tag, Operation<Void> original) {
if (entity.getLevel() == null) {
// The block entity is not in a level, just follow the default logic.
original.call(tag);
return;
}

try (ProblemReporter.ScopedCollector reporter = new ProblemReporter.ScopedCollector(LOGGER)) {
ValueInput data = TagValueInput.create(reporter, this.entity.getLevel().registryAccess(), tag);
DataAccessorHandler.applyDataChanges(this.entity, data, () -> original.call(tag));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.nbt.CompoundTag;
import net.minecraft.server.commands.data.DataAccessor;
import net.minecraft.server.commands.data.EntityDataAccessor;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.storage.TagValueInput;
import net.minecraft.world.level.storage.ValueInput;

import net.fabricmc.fabric.impl.attachment.DataAccessorHandler;

@Mixin(EntityDataAccessor.class)
public abstract class EntityDataAccessorMixin implements DataAccessor {
@Unique
private static final Logger LOGGER = LoggerFactory.getLogger("EntityDataAccessorMixin");

@Shadow
@Final
private Entity entity;

@WrapMethod(method = "setData")
public void setData(CompoundTag tag, Operation<Void> original) {
if (entity.level() == null) {
// The block entity is not in a level, just follow the default logic.
original.call(tag);
return;
}

try (ProblemReporter.ScopedCollector reporter = new ProblemReporter.ScopedCollector(LOGGER)) {
ValueInput data = TagValueInput.create(reporter, this.entity.level().registryAccess(), tag);
DataAccessorHandler.applyDataChanges(this.entity, data, () -> original.call(tag));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
"mixins": [
"AttachmentTargetsMixin",
"BannerBlockEntityMixin",
"BlockDataAccessorMixin",
"BlockEntityMixin",
"ChunkAccessMixin",
"ChunkHolderMixin",
"ClientboundCustomPayloadPacketAccessor",
"DimensionStorageFileFixMixin",
"EntityDataAccessorMixin",
"EntityMixin",
"ImposterProtoChunkMixin",
"LevelChunkMixin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ void applyToInvalidTarget() throws AttachmentSyncException {
* so testing is handled by the testmod instead.
*/

private static RegistryAccess mockRA() {
static RegistryAccess mockRA() {
RegistryAccess ra = mock(RegistryAccess.class);
when(ra.createSerializationContext(any())).thenReturn((RegistryOps<Object>) (Object) RegistryOps.create(NbtOps.INSTANCE, ra));
return ra;
Expand Down
Loading
Loading