Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 13 additions & 0 deletions src/main/java/com/modularmc/synceddata/SyncedData.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.modularmc.synceddata;

import com.modularmc.synceddata.api.sync_system.SyncedComponents;
import com.modularmc.synceddata.api.sync_system.network.ClientBlockEntitySyncPayload;
import com.modularmc.synceddata.api.sync_system.network.ServerBlockEntitySyncPayload;
import com.modularmc.synceddata.utils.FormattingUtil;

import net.minecraft.client.Minecraft;
Expand All @@ -12,6 +14,8 @@
import net.neoforged.fml.loading.FMLEnvironment;
import net.neoforged.fml.loading.FMLPaths;
import net.neoforged.neoforge.data.loading.DatagenModLoader;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
import net.neoforged.neoforge.server.ServerLifecycleHooks;

import com.mojang.serialization.Codec;
Expand All @@ -34,6 +38,15 @@ public class SyncedData {

public SyncedData(IEventBus bus) {
SyncedComponents.COMPONENTS.register(bus);
bus.addListener(SyncedData::registerPayloadHandlers);
}

private static void registerPayloadHandlers(RegisterPayloadHandlersEvent event) {
PayloadRegistrar registrar = event.registrar(MOD_ID);
registrar.playToClient(ServerBlockEntitySyncPayload.TYPE, ServerBlockEntitySyncPayload.CODEC,
ServerBlockEntitySyncPayload::execute);
registrar.playToServer(ClientBlockEntitySyncPayload.TYPE, ClientBlockEntitySyncPayload.CODEC,
ClientBlockEntitySyncPayload::execute);
}

public static Identifier id(String path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.modularmc.synceddata.api.blockentity.BlockEntityCreationInfo;
import com.modularmc.synceddata.api.sync_system.holder.SyncDataHolder;
import com.modularmc.synceddata.api.sync_system.network.ClientBlockEntitySyncPayload;
import com.modularmc.synceddata.api.sync_system.network.ServerBlockEntitySyncPayload;

import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
Expand All @@ -13,12 +15,14 @@
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.neoforged.neoforge.client.network.ClientPacketDistributor;
import net.neoforged.neoforge.network.PacketDistributor;

import com.mojang.serialization.MapCodec;
import lombok.Getter;
Expand Down Expand Up @@ -105,13 +109,26 @@ public final void updateTick() {
setChanged();
if (getLevel() instanceof ServerLevel serverLevel) {
if (syncDataHolder.scanAndMarkChanges(serverLevel.registryAccess())) {
serverLevel.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(),
Block.UPDATE_CLIENTS);
byte[] data = syncDataHolder.collectClientNetworkChanges(serverLevel.registryAccess(), false);
if (data.length > 0) {
PacketDistributor.sendToPlayersTrackingChunk(serverLevel,
new ChunkPos(getBlockPos().getX() >> 4, getBlockPos().getZ() >> 4),
ServerBlockEntitySyncPayload.of(this, data));
}
}
}
}

public final void handleClientUpdate(HolderLookup.Provider registries, CompoundTag tag) {
syncDataHolder.applyServerUpdate(registries, tag);
public final void handleClientUpdate(net.minecraft.core.RegistryAccess registries, byte[] data) {
syncDataHolder.applyServerNetworkUpdate(registries, data);
}

public final void pushClientChangesToServer() {
if (getLevel() instanceof ClientLevel clientLevel) {
byte[] changes = syncDataHolder.collectServerNetworkChanges(clientLevel.registryAccess());
if (changes.length > 0) {
ClientPacketDistributor.sendToServer(new ClientBlockEntitySyncPayload(getBlockPos().asLong(), changes));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
import com.modularmc.synceddata.api.sync_system.meta.FieldSyncData;

import net.minecraft.core.HolderLookup;
import net.minecraft.core.RegistryAccess;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.world.item.ItemStack;

import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;

import java.util.*;

Expand All @@ -22,15 +27,15 @@ public class SyncDataHolder {
private final ClassSyncData syncData;
private final ISyncManaged holder;

private final Map<String, Object> cachedValues = new HashMap<>();
private final Map<FieldSyncData, Object> cachedValues = new Reference2ReferenceOpenHashMap<>();
private CompoundTag pendingClientChanges = null;
private boolean fullSyncPending = true;

public SyncDataHolder(ISyncManaged o) {
holder = o;
syncData = ClassSyncData.getClassData(o.getClass());
for (FieldSyncData field : syncData.getClientSyncFields()) {
cachedValues.put(field.fieldName, field.handle.get(holder));
cachedValues.put(field, field.handle.get(holder));
}
}

Expand All @@ -44,12 +49,12 @@ public boolean scanAndMarkChanges(HolderLookup.Provider registries) {

for (FieldSyncData field : syncData.getClientSyncFields()) {
Object currentValue = field.handle.get(holder);
Object previousValue = cachedValues.get(field.fieldName);
Object previousValue = cachedValues.get(field);
boolean changed = fullSyncPending || !Objects.equals(currentValue, previousValue);
if (changed) {
Tag serialized = encodeField(field, currentValue, registries);
changes.put(field.nbtSaveKey, serialized);
cachedValues.put(field.fieldName, currentValue);
cachedValues.put(field, currentValue);
hasChanges = true;
}
}
Expand All @@ -67,6 +72,84 @@ public CompoundTag getPendingChanges() {
return changes != null ? changes : new CompoundTag();
}

public byte[] collectClientNetworkChanges(RegistryAccess registries, boolean force) {
if (force) {
CompoundTag forcedChanges = new CompoundTag();
for (FieldSyncData field : syncData.getClientSyncFields()) {
Object currentValue = field.handle.get(holder);
forcedChanges.put(field.nbtSaveKey, encodeField(field, currentValue, registries));
cachedValues.put(field, currentValue);
}
pendingClientChanges = forcedChanges;
fullSyncPending = false;
}

CompoundTag pendingChanges = getPendingChanges();
if (pendingChanges.isEmpty()) {
return new byte[0];
}

RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), registries);
try {
FieldSyncData[] fields = syncData.getOrderedClientSyncFields();
for (int i = 0; i < fields.length; i++) {
FieldSyncData field = fields[i];
Tag value = pendingChanges.get(field.nbtSaveKey);
if (value == null) {
continue;
}
buf.writeVarInt(i);
buf.writeNbt(value);
}
byte[] data = new byte[buf.readableBytes()];
buf.getBytes(0, data);
return data;
} finally {
buf.release();
}
}

public CompoundTag collectServerChanges(HolderLookup.Provider registries) {
CompoundTag changes = new CompoundTag();
for (FieldSyncData field : syncData.getServerUpdateFields()) {
Object currentValue = field.handle.get(holder);
Object previousValue = cachedValues.get(field);
if (!Objects.equals(currentValue, previousValue)) {
changes.put(field.fieldName, encodeField(field, currentValue, registries));
cachedValues.put(field, currentValue);
}
}
return changes;
}

public byte[] collectServerNetworkChanges(RegistryAccess registries) {
RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), registries);
try {
boolean wroteAny = false;
FieldSyncData[] fields = syncData.getOrderedServerUpdateFields();
for (int i = 0; i < fields.length; i++) {
FieldSyncData field = fields[i];
Object currentValue = field.handle.get(holder);
Object previousValue = cachedValues.get(field);
if (Objects.equals(currentValue, previousValue)) {
continue;
}
buf.writeVarInt(i);
buf.writeNbt(encodeField(field, currentValue, registries));
cachedValues.put(field, currentValue);
wroteAny = true;
}
if (!wroteAny) {
return new byte[0];
}
byte[] data = new byte[buf.readableBytes()];
buf.getBytes(0, data);
return data;
} finally {
buf.release();
}
}

public CompoundTag serializeToSaveNBT(HolderLookup.Provider registries) {
CompoundTag tag = new CompoundTag();
for (var field : syncData.getWorldSaveFields()) {
Expand All @@ -93,7 +176,7 @@ public CompoundTag serializeFullClientSyncNBT(HolderLookup.Provider registries)
Object value = field.handle.get(holder);
Tag serialized = encodeField(field, value, registries);
tag.put(field.nbtSaveKey, serialized);
cachedValues.put(field.fieldName, value);
cachedValues.put(field, value);
}
fullSyncPending = false;
return tag;
Expand All @@ -113,7 +196,7 @@ public void deserializeNBT(HolderLookup.Provider registries, CompoundTag tag, bo
}

if (readingClientFields) {
cachedValues.put(field.fieldName, field.handle.get(holder));
cachedValues.put(field, field.handle.get(holder));
for (var listener : field.changeListenerHandles) {
try {
listener.invoke(holder);
Expand All @@ -140,11 +223,7 @@ public void deserializeItemNBT(HolderLookup.Provider registries, CompoundTag tag
}

public void applyServerUpdate(HolderLookup.Provider registries, CompoundTag tag) {
Set<FieldSyncData> targetFields = new HashSet<>();
targetFields.addAll(syncData.getServerSyncFields());
targetFields.addAll(syncData.getBothSyncFields());

for (var field : targetFields) {
for (var field : syncData.getServerUpdateFields()) {
Tag value = tag.get(field.fieldName);
if (value != null) {
Object decoded = decodeField(field, value, field.handle.get(holder), registries);
Expand All @@ -155,6 +234,70 @@ public void applyServerUpdate(HolderLookup.Provider registries, CompoundTag tag)
}
}

public void applyServerNetworkUpdate(RegistryAccess registries, byte[] data) {
if (data.length == 0) {
return;
}
RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(data), registries);
try {
FieldSyncData[] fields = syncData.getOrderedServerUpdateFields();
while (buf.isReadable()) {
int index = buf.readVarInt();
if (index < 0 || index >= fields.length) {
throw new IllegalArgumentException("Invalid server sync field index: " + index);
}
FieldSyncData field = fields[index];
Tag value = buf.readNbt(NbtAccounter.unlimitedHeap());
if (value != null) {
Object decoded = decodeField(field, value, field.handle.get(holder), registries);
if (decoded != null) {
field.handle.set(holder, decoded);
}
}
}
} finally {
buf.release();
}
}

public void applyClientNetworkUpdate(RegistryAccess registries, byte[] data) {
if (data.length == 0) {
return;
}
RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(data), registries);
try {
FieldSyncData[] fields = syncData.getOrderedClientSyncFields();
while (buf.isReadable()) {
int index = buf.readVarInt();
if (index < 0 || index >= fields.length) {
throw new IllegalArgumentException("Invalid client sync field index: " + index);
}
FieldSyncData field = fields[index];
Tag value = buf.readNbt(NbtAccounter.unlimitedHeap());
if (value != null) {
Object decoded = decodeField(field, value, field.handle.get(holder), registries);
if (decoded != null) {
field.handle.set(holder, decoded);
}
cachedValues.put(field, field.handle.get(holder));
for (var listener : field.changeListenerHandles) {
try {
listener.invoke(holder);
} catch (Throwable e) {
SyncedData.LOGGER.error("Sync: Error invoking change listener for field {}", field.fieldName);
SyncedData.LOGGER.error(e);
}
}
if (field.triggerClientRerender) {
holder.scheduleRenderUpdate();
}
}
}
} finally {
buf.release();
}
}

public void applyToItemStack(ItemStack stack, HolderLookup.Provider registries) {
CompoundTag data = serializeToItemNBT(registries);
if (!data.isEmpty()) {
Expand All @@ -176,7 +319,6 @@ private Tag encodeField(FieldSyncData field, Object value, HolderLookup.Provider
return nullTag;
}
if (field.codec != null) {
@SuppressWarnings("unchecked")
Codec<Object> codec = field.codec;
DataResult<Tag> result = codec.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), value);
return result.getOrThrow();
Expand All @@ -196,7 +338,6 @@ private Object decodeField(FieldSyncData field, Tag tag, Object currentValue, Ho
return currentValue;
}
if (field.codec != null) {
@SuppressWarnings("unchecked")
Codec<Object> codec = field.codec;
DataResult<Object> result = codec.parse(registries.createSerializationContext(NbtOps.INSTANCE), tag);
return result.getOrThrow();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.modularmc.synceddata.api.sync_system.network;

import com.modularmc.synceddata.SyncedData;
import com.modularmc.synceddata.api.sync_system.ManagedSyncBlockEntity;

import net.minecraft.core.BlockPos;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.neoforge.network.handling.IPayloadContext;

import org.jspecify.annotations.NonNull;

public record ClientBlockEntitySyncPayload(long pos, byte[] data) implements CustomPacketPayload {

public static final Identifier ID = SyncedData.id("client_be_sync");
public static final Type<ClientBlockEntitySyncPayload> TYPE = new Type<>(ID);
public static final StreamCodec<RegistryFriendlyByteBuf, ClientBlockEntitySyncPayload> CODEC = StreamCodec.ofMember(
ClientBlockEntitySyncPayload::write,
ClientBlockEntitySyncPayload::decode);

@Override
public @NonNull Type<? extends CustomPacketPayload> type() {
return TYPE;
}

private void write(RegistryFriendlyByteBuf buf) {
buf.writeLong(pos);
buf.writeByteArray(data);
}

private static ClientBlockEntitySyncPayload decode(RegistryFriendlyByteBuf buf) {
return new ClientBlockEntitySyncPayload(buf.readLong(), buf.readByteArray());
}

public static void execute(ClientBlockEntitySyncPayload packet, IPayloadContext context) {
if (!(context.player() instanceof ServerPlayer serverPlayer)) {
return;
}
if (!(serverPlayer.level().getBlockEntity(BlockPos.of(packet.pos)) instanceof ManagedSyncBlockEntity blockEntity)) {
return;
}
blockEntity.handleClientUpdate(serverPlayer.level().registryAccess(), packet.data);
blockEntity.markAsChanged();
}
}
Loading
Loading