diff --git a/docs/content/Development/Data-Sync-System/Usage.md b/docs/content/Development/Data-Sync-System/Usage.md index 637c8b8bb13..e7310d3b0c7 100644 --- a/docs/content/Development/Data-Sync-System/Usage.md +++ b/docs/content/Development/Data-Sync-System/Usage.md @@ -6,28 +6,59 @@ title: "Usage" ### Registering classes with the sync system -At the core of the system is the interface `ISyncManaged`, which represents a class that to be synchronised with the client or saved. +At the core of the system are the `ISyncManaged` and `ISyncAnnotated` interfaces, which allow for their fields to have sync annotations.. All block entities which should be synchronised or saved must extend the abstract class `ManagedSyncBlockEntity`. +`ISyncManaged` should be used for classes with any form of persistent state, and cannot be instantiated or reassigned.
+`ISyncAnnotated` should be used for record-like classes that hold data, and must define a no-args constructor. + + !!! warning Block entities that inherit `ManagedSyncBlockEntity` must call `ManagedSyncBlockEntity::updateTick`***every tick*** within their ticker, or they will not be saved. +#### Example of `ISyncManaged` usage ```java class MySyncObject implements ISyncManaged { - // Any class that directly implements ISyncManaged must have the following: + + // Any class that directly implements ISyncManaged must have a SyncDataHolder: @Getter protected final SyncDataHolder syncDataHolder = new SyncDataHolder(this); + + // ISyncManaged objects should be attached to a parent sync managed object, + // unless the sync managed object is a blockentity + // ISyncManaged classes must implement a getter for their parent sync object + @Getter + private final MetaMachine parentSyncObject; + + @SaveField + @SyncToClient + private BlockPos syncPos; + @SaveField + @SyncToClient + private ExampleSyncAnnotated syncAnnotatedField; + + public MySyncObject(MetaMachine machine) { + this.parentSyncObject = machine; + } + + public void doChanges() { + + syncPos = BlockPos.ZERO; + // Client sync fields do not update automatically. + getSyncDataHolder().markClientSyncFieldDirty("syncPos") + + syncAnnotatedField.someValue = 10; + /* + * Because ISyncAnnotated classes do not manage their own sync state, + * updating a field in an ISyncAnnotated class requires the parent field to be marked as changed. + */ + getSyncDataHolder().markClientSyncFieldDirty("syncAnnotatedField"); + } - /** - * Function called when the SyncDataHolder requests a rerender - */ - void scheduleRenderUpdate(); - - /** - * Function called to notify the server that this object has been updated and must be synced to clients - */ - void markAsChanged(); + private static class ExampleSyncAnnotated implements ISyncAnnotated { + public int someValue = 0; + } } ``` diff --git a/docs/content/Development/General-Topics/Tick-Updates.md b/docs/content/Development/General-Topics/Tick-Updates.md index 328dc2c17a1..6052e30a41a 100644 --- a/docs/content/Development/General-Topics/Tick-Updates.md +++ b/docs/content/Development/General-Topics/Tick-Updates.md @@ -61,10 +61,9 @@ Lets look at how we implement it in `QuantumChest`. @Override public void onLoad() { super.onLoad(); - if (getLevel() instanceof ServerLevel serverLevel) { - // you cant call ItemTransferHelper.getItemTransfer while chunk is loading, so lets defer it next tick. - serverLevel.getServer().tell(new TickTask(0, this::updateAutoOutputSubscription)); - } + + // you cant call ItemTransferHelper.getItemTransfer while chunk is loading, so lets defer it next tick. + scheduleForNextServerTick(this::updateAutoOutputSubscription); // add a listener to listen the changes of inner inventory. (for ex, if inventory not empty anymore, we may need to unpdate logic) exportItemSubs = cache.addChangedListener(this::updateAutoOutputSubscription); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/block/MetaMachineBlock.java b/src/main/java/com/gregtechceu/gtceu/api/block/MetaMachineBlock.java index b52a2a787dc..fedfd189d7a 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/block/MetaMachineBlock.java +++ b/src/main/java/com/gregtechceu/gtceu/api/block/MetaMachineBlock.java @@ -8,7 +8,6 @@ import com.gregtechceu.gtceu.api.machine.MetaMachine; import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; import com.gregtechceu.gtceu.api.machine.feature.*; -import com.gregtechceu.gtceu.api.sync_system.ManagedSyncBlockEntity; import com.gregtechceu.gtceu.common.machine.owner.MachineOwner; import com.gregtechceu.gtceu.utils.ExtendedUseOnContext; import com.gregtechceu.gtceu.utils.GTUtil; @@ -392,13 +391,9 @@ public BlockEntityTicker getTicker(Level level, Block if (blockEntityType == getDefinition().getBlockEntityType()) { if (!level.isClientSide) { return (pLevel, pPos, pState, pTile) -> { - pTile.setChanged(); if (pTile instanceof MetaMachine metaMachine) { metaMachine.serverTick(); } - if (pTile instanceof ManagedSyncBlockEntity syncObj) { - syncObj.updateTick(); - } }; } else { return (pLevel, pPos, pState, pTile) -> { diff --git a/src/main/java/com/gregtechceu/gtceu/api/block/PipeBlock.java b/src/main/java/com/gregtechceu/gtceu/api/block/PipeBlock.java index d71b2bebf34..9d51a646986 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/block/PipeBlock.java +++ b/src/main/java/com/gregtechceu/gtceu/api/block/PipeBlock.java @@ -14,7 +14,6 @@ import com.gregtechceu.gtceu.api.pipenet.LevelPipeNet; import com.gregtechceu.gtceu.api.pipenet.PipeNet; import com.gregtechceu.gtceu.api.registry.registrate.provider.GTBlockstateProvider; -import com.gregtechceu.gtceu.api.sync_system.ManagedSyncBlockEntity; import com.gregtechceu.gtceu.client.model.pipe.PipeModel; import com.gregtechceu.gtceu.common.data.GTItems; import com.gregtechceu.gtceu.common.data.GTMaterialBlocks; @@ -447,9 +446,6 @@ public BlockEntityTicker getTicker(Level level, Block if (pTile instanceof IPipeNode pipeNode) { pipeNode.serverTick(); } - if (pTile instanceof ManagedSyncBlockEntity syncObj) { - syncObj.updateTick(); - } }; } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/blockentity/IGregtechBlockEntity.java b/src/main/java/com/gregtechceu/gtceu/api/blockentity/IGregtechBlockEntity.java index 866c0cfadde..821f057094f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/blockentity/IGregtechBlockEntity.java +++ b/src/main/java/com/gregtechceu/gtceu/api/blockentity/IGregtechBlockEntity.java @@ -42,8 +42,6 @@ default void scheduleNeighborShapeUpdate() { level.getBlockState(pos).updateNeighbourShapes(level, pos, Block.UPDATE_ALL); } - void markAsChanged(); - default boolean isRemote() { return getLevel() == null ? GTCEu.isClientThread() : getLevel().isClientSide; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/blockentity/PipeBlockEntity.java b/src/main/java/com/gregtechceu/gtceu/api/blockentity/PipeBlockEntity.java index d0d0a1babe8..c00a275b420 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/blockentity/PipeBlockEntity.java +++ b/src/main/java/com/gregtechceu/gtceu/api/blockentity/PipeBlockEntity.java @@ -184,6 +184,7 @@ public void unsubscribe(@Nullable TickableSubscription current) { } public final void serverTick() { + super.serverTick(); if (!waitingToAdd.isEmpty()) { serverTicks.addAll(waitingToAdd); waitingToAdd.clear(); @@ -308,13 +309,6 @@ public boolean triggerEvent(int id, int para) { return false; } - @Override - public void setChanged() { - if (getLevel() != null) { - getLevel().blockEntityChanged(getBlockPos()); - } - } - ////////////////////////////////////// // ******* Interaction *******// ////////////////////////////////////// @@ -358,14 +352,14 @@ public UITexture getPipeTexture(boolean isBlock) { if (player == null) return Pair.of(null, InteractionResult.PASS); // Prioritize covers - var cover = getCoverContainer().getCoverAtSide(context.getClickedFace()); + CoverBehavior cover = getCoverContainer().getCoverAtSide(context.getGridSide()); if (cover != null) { var result = cover.onToolClick(context); if (result.getSecond() != InteractionResult.PASS) return result; - if (toolType.contains(GTToolType.CROWBAR) && !isRemote()) { + if (toolType.contains(GTToolType.CROWBAR)) { getCoverContainer().removeCover(context.getGridSide(), player); - return Pair.of(GTToolType.CROWBAR, InteractionResult.SUCCESS); + return Pair.of(GTToolType.CROWBAR, InteractionResult.sidedSuccess(isRemote())); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/ICoverable.java b/src/main/java/com/gregtechceu/gtceu/api/capability/ICoverable.java index d06645b7fc5..e775e766df1 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/ICoverable.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/ICoverable.java @@ -41,6 +41,11 @@ public interface ICoverable extends ITickSubscription, ISyncManaged, ICopyable { IGregtechBlockEntity getHolder(); + @Override + default ISyncManaged getParentSyncObject() { + return getHolder(); + } + default Level getLevel() { return getHolder().getLevel(); } @@ -65,18 +70,10 @@ default void notifyBlockUpdate() { getHolder().notifyBlockUpdate(); } - default void scheduleRenderUpdate() { - getHolder().notifyBlockUpdate(); - } - default void scheduleNeighborShapeUpdate() { getHolder().scheduleNeighborShapeUpdate(); } - default void markAsChanged() { - getHolder().markAsChanged(); - } - @Nullable @Override default TickableSubscription subscribeServerTick(Runnable runnable) { diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/CoverBehavior.java b/src/main/java/com/gregtechceu/gtceu/api/cover/CoverBehavior.java index 8b948e23b28..4503cf83047 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/CoverBehavior.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/CoverBehavior.java @@ -67,16 +67,9 @@ public CoverBehavior(CoverDefinition definition, ICoverable coverHolder, Directi this.attachedSide = attachedSide; } - ////////////////////////////////////// - // ***** Initialization ******// - ////////////////////////////////////// - public void scheduleRenderUpdate() { - coverHolder.scheduleRenderUpdate(); - } - @Override - public void markAsChanged() { - coverHolder.markAsChanged(); + public ISyncManaged getParentSyncObject() { + return coverHolder; } /** diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandler.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandler.java index ac2f13b4d4f..5373c9113cf 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandler.java @@ -153,12 +153,7 @@ private void loadFilterFromItem() { } @Override - public void markAsChanged() { - container.markAsChanged(); - } - - @Override - public void scheduleRenderUpdate() { - container.scheduleRenderUpdate(); + public @Nullable ISyncManaged getParentSyncObject() { + return container; } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/ConditionalSubscriptionHandler.java b/src/main/java/com/gregtechceu/gtceu/api/machine/ConditionalSubscriptionHandler.java index cc65f0d1002..fc5f3446833 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/ConditionalSubscriptionHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/ConditionalSubscriptionHandler.java @@ -8,6 +8,8 @@ import net.minecraft.util.thread.BlockableEventLoop; import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; + import java.util.function.BooleanSupplier; /** @@ -22,7 +24,7 @@ public class ConditionalSubscriptionHandler { private final Runnable runnable; private final BooleanSupplier condition; - private TickableSubscription subscription; + private @Nullable TickableSubscription subscription; public ConditionalSubscriptionHandler(ITickSubscription handler, Runnable runnable, BooleanSupplier condition) { this.handler = handler; diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/MetaMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/MetaMachine.java index 76ab5393e0e..f3919a238c1 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/MetaMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/MetaMachine.java @@ -57,6 +57,8 @@ import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; +import net.minecraft.server.TickTask; +import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.RandomSource; import net.minecraft.world.InteractionHand; @@ -130,6 +132,8 @@ public class MetaMachine extends ManagedSyncBlockEntity implements IGregtechBloc private final long offset = GTValues.RNG.nextInt(20); @Getter + @SaveField + @SyncToClient protected final MachineTraitHolder traitHolder; private final List serverTicks; @@ -155,9 +159,10 @@ public void load(CompoundTag tag) { } /** - * Called when this machine is loaded. The entire world is not loaded when this method is called. - * To schedule code to run on the first full world tick, do - * {@code serverLevel.getServer().tell(new TickTask(0, CALLBACK))} + * Called when this machine is loaded.
+ * On the server side, the entire world may not be loaded when this method is called.
+ * On the client side, this method is called before this machine's data has been received.
+ * To schedule code to run on the first full world tick, see {@link #scheduleForNextServerTick(Runnable)} */ @MustBeInvokedByOverriders public void onLoad() { @@ -171,6 +176,18 @@ public void onLoad() { } } + /** + * Schedules a callback to be executed on the next server tick. Only works on the server-side.
+ * Should be called from methods such as {@link #onLoad()}, when the world may not be fully loaded. + * + * @param runnable The callback to execute + */ + public final void scheduleForNextServerTick(Runnable runnable) { + if (getLevel() instanceof ServerLevel serverLevel) { + serverLevel.getServer().tell(new TickTask(0, runnable)); + } + } + @Override public final void setRemoved() { super.setRemoved(); @@ -234,6 +251,7 @@ public void unsubscribe(@Nullable TickableSubscription current) { @ApiStatus.Internal public final void serverTick() { + super.serverTick(); executeTick(); } @@ -397,14 +415,14 @@ public Optional getTraitOptional(MachineTraitType Pair<@Nullable GTToolType, InteractionResult> result = null; // Prioritize covers - var cover = getCoverContainer().getCoverAtSide(context.getClickedFace()); + CoverBehavior cover = getCoverContainer().getCoverAtSide(context.getGridSide()); if (cover != null) { result = cover.onToolClick(context); if (result.getSecond() != InteractionResult.PASS) return result; - if (toolType.contains(GTToolType.CROWBAR) && !isRemote()) { + if (toolType.contains(GTToolType.CROWBAR)) { getCoverContainer().removeCover(context.getGridSide(), player); - return Pair.of(GTToolType.CROWBAR, InteractionResult.SUCCESS); + return Pair.of(GTToolType.CROWBAR, InteractionResult.sidedSuccess(isRemote())); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamBoilerMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamBoilerMachine.java index e5b181ff6e7..030ee27c750 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamBoilerMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamBoilerMachine.java @@ -24,7 +24,6 @@ import net.minecraft.core.Direction; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.network.chat.Component; -import net.minecraft.server.TickTask; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; @@ -91,10 +90,9 @@ protected NotifiableFluidTank createWaterTank() { @Override public void onLoad() { super.onLoad(); - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateAutoOutputSubscription)); - } - updateSteamSubscription(); + + scheduleForNextServerTick(this::updateAutoOutputSubscription); + scheduleForNextServerTick(this::updateSteamSubscription); steamTankSubs = steamTank.addChangedListener(this::updateAutoOutputSubscription); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MachineTrait.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MachineTrait.java index c6d8669b9b4..71413e83175 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MachineTrait.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MachineTrait.java @@ -56,6 +56,11 @@ public MetaMachine getMachine() { return machine; } + @Override + public @Nullable ISyncManaged getParentSyncObject() { + return getMachine(); + } + /** * A list containing the machine classes which this trait can be attached to. * If this trait is being attached to a machine class that does not conform to any of the list elements, an @@ -117,14 +122,10 @@ public void setRenderState(MachineRenderState state) { getMachine().setRenderState(state); } - public void scheduleRenderUpdate() { - getMachine().scheduleRenderUpdate(); - } - /** * Called when the machine is loaded. The entire world is not loaded when this method is called. * To schedule code to run on the first full world tick, do - * {@code serverLevel.getServer().tell(new TickTask(0, CALLBACK))} + * {@code getMachine().scheduleForNextServerTick(callback)} */ public void onMachineLoad() {} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java index 91ebc16a6cc..90072795e60 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java @@ -16,12 +16,12 @@ import com.gregtechceu.gtceu.api.recipe.modifier.ModifierFunction; import com.gregtechceu.gtceu.api.registry.GTRegistries; import com.gregtechceu.gtceu.api.sound.AutoReleasedSound; +import com.gregtechceu.gtceu.api.sync_system.ClassSyncData; import com.gregtechceu.gtceu.api.sync_system.annotations.ClientFieldChangeListener; import com.gregtechceu.gtceu.api.sync_system.annotations.RerenderOnChanged; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; import com.gregtechceu.gtceu.api.sync_system.annotations.SyncToClient; import com.gregtechceu.gtceu.api.sync_system.data_transformers.ValueTransformer; -import com.gregtechceu.gtceu.api.sync_system.data_transformers.ValueTransformers; import com.gregtechceu.gtceu.common.cover.MachineControllerCover; import com.gregtechceu.gtceu.utils.GTMath; @@ -52,8 +52,6 @@ public MachineTraitType getTraitType() { return TYPE; } - protected static class ChanceCacheMap extends IdentityHashMap, Object2IntMap> {} - public enum Status implements StringRepresentable { IDLE("idle"), @@ -137,7 +135,7 @@ public enum Status implements StringRepresentable { protected boolean suspendAfterFinish = false; @Getter @SaveField(nbtKey = "chance_cache") - protected final ChanceCacheMap chanceCaches = makeChanceCaches(); + protected final IdentityHashMap, Object2IntMap> chanceCaches = makeChanceCaches(); protected @Nullable TickableSubscription subscription; protected @Nullable Object workingSound; @@ -632,8 +630,8 @@ public List getWaitingReasons() { return Collections.emptyList(); } - protected ChanceCacheMap makeChanceCaches() { - ChanceCacheMap map = new ChanceCacheMap(); + protected IdentityHashMap, Object2IntMap> makeChanceCaches() { + IdentityHashMap, Object2IntMap> map = new IdentityHashMap<>(); for (RecipeCapability cap : GTRegistries.RECIPE_CAPABILITIES.values()) { map.put(cap, cap.makeChanceCache()); } @@ -641,56 +639,60 @@ protected ChanceCacheMap makeChanceCaches() { } static { - ValueTransformers.registerTransformer(ChanceCacheMap.class, new ValueTransformer() { - - @Override - public Tag serializeNBT(ChanceCacheMap value, - TransformerContext context) { - CompoundTag chanceCache = new CompoundTag(); - if (context.currentValue() == null) return chanceCache; - - context.currentValue().forEach((cap, cache) -> { - ListTag cacheTag = new ListTag(); - for (var entry : cache.object2IntEntrySet()) { - CompoundTag compoundTag = new CompoundTag(); - var obj = cap.contentToNbt(entry.getKey()); - compoundTag.put("entry", obj); - compoundTag.putInt("cached_chance", entry.getIntValue()); - cacheTag.add(compoundTag); - } - chanceCache.put(cap.name, cacheTag); - }); + ClassSyncData.getClassData(RecipeLogic.class) + .setCustomTransformerForField("chanceCaches", + new ValueTransformer, Object2IntMap>>() { + + @Override + public Tag serializeNBT(IdentityHashMap, Object2IntMap> value, + TransformerContext, Object2IntMap>> context) { + CompoundTag chanceCache = new CompoundTag(); + if (context.currentValue() == null) return chanceCache; + + context.currentValue().forEach((cap, cache) -> { + ListTag cacheTag = new ListTag(); + for (var entry : cache.object2IntEntrySet()) { + CompoundTag compoundTag = new CompoundTag(); + var obj = cap.contentToNbt(entry.getKey()); + compoundTag.put("entry", obj); + compoundTag.putInt("cached_chance", entry.getIntValue()); + cacheTag.add(compoundTag); + } + chanceCache.put(cap.name, cacheTag); + }); - return chanceCache; - } + return chanceCache; + } - @Override - public @Nullable ChanceCacheMap deserializeNBT(Tag tag, - TransformerContext context) { - CompoundTag chanceCache = ValueTransformer.assertTagType(CompoundTag.class, tag, context); - if (context.currentValue() != null) { - for (String key : chanceCache.getAllKeys()) { - RecipeCapability cap = GTRegistries.RECIPE_CAPABILITIES.get(key); - // Necessary since a RecipeCapability was removed when removing Create support, and for future - // removals - if (cap == null) continue; - // noinspection rawtypes - Object2IntMap map = context.currentValue().computeIfAbsent(cap, - RecipeCapability::makeChanceCache); - - ListTag chanceTag = chanceCache.getList(key, Tag.TAG_COMPOUND); - for (int i = 0; i < chanceTag.size(); ++i) { - CompoundTag chanceKey = chanceTag.getCompound(i); - var entry = cap.serializer.fromNbt(chanceKey.get("entry")); - int value = chanceKey.getInt("cached_chance"); - // noinspection unchecked - map.put(entry, value); - } - } - } - return context.currentValue(); - } - }); + @Override + public @Nullable IdentityHashMap, Object2IntMap> deserializeNBT(Tag tag, + TransformerContext, Object2IntMap>> context) { + CompoundTag chanceCache = ValueTransformer.assertTagType(CompoundTag.class, tag, + context); + if (context.currentValue() != null) { + for (String key : chanceCache.getAllKeys()) { + RecipeCapability cap = GTRegistries.RECIPE_CAPABILITIES.get(key); + // Necessary since a RecipeCapability was removed when removing Create support, + // and for future + // removals + if (cap == null) continue; + // noinspection rawtypes + Object2IntMap map = context.currentValue().computeIfAbsent(cap, + RecipeCapability::makeChanceCache); + + ListTag chanceTag = chanceCache.getList(key, Tag.TAG_COMPOUND); + for (int i = 0; i < chanceTag.size(); ++i) { + CompoundTag chanceKey = chanceTag.getCompound(i); + var entry = cap.serializer.fromNbt(chanceKey.get("entry")); + int value = chanceKey.getInt("cached_chance"); + // noinspection unchecked + map.put(entry, value); + } + } + } + return context.currentValue(); + } + }); } public static void putFailureReason(Object machine, GTRecipe recipe, Component reason) { diff --git a/src/main/java/com/gregtechceu/gtceu/api/pipenet/IPipeNode.java b/src/main/java/com/gregtechceu/gtceu/api/pipenet/IPipeNode.java index 8358ed88f6a..2bc8a9b7ac2 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/pipenet/IPipeNode.java +++ b/src/main/java/com/gregtechceu/gtceu/api/pipenet/IPipeNode.java @@ -11,11 +11,9 @@ import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.client.model.data.ModelData; -import net.minecraftforge.client.model.data.ModelDataManager; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -123,26 +121,6 @@ default NodeDataType getNodeData() { void notifyBlockUpdate(); - @SuppressWarnings("UnstableApiUsage") - default void scheduleRenderUpdate() { - var pos = getBlockPos(); - var level = getLevel(); - if (level != null) { - var state = level.getBlockState(pos); - if (level.isClientSide) { - // simplified from requestModelDataUpdate - ModelDataManager manager = level.getModelDataManager(); - if (manager != null) { - manager.requestRefresh(self()); - } - - level.sendBlockUpdated(pos, state, state, Block.UPDATE_IMMEDIATE); - } else { - level.blockEvent(pos, state.getBlock(), 1, 0); - } - } - } - default void serverTick() {} @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/ClassSyncData.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/ClassSyncData.java index cf648d12ba9..9deabe394d5 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/sync_system/ClassSyncData.java +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/ClassSyncData.java @@ -10,6 +10,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.Getter; +import lombok.SneakyThrows; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -45,14 +46,22 @@ public static ClassSyncData getClassData(Class cls) { @Getter private final Set serverSaveFields = new ObjectOpenHashSet<>(); + @SneakyThrows private ClassSyncData(Class clazz) { + var isManaged = ISyncManaged.class.isAssignableFrom(clazz); + var isAnnotated = ISyncAnnotated.class.isAssignableFrom(clazz); + + if (!isManaged && !isAnnotated) + throw new IllegalArgumentException("Cannot create class sync data for non-sync class"); + if (isManaged && isAnnotated) throw new IllegalArgumentException( + "Class %s cannot inherit both ISyncAnnotated and ISyncManaged".formatted(clazz.getName())); + MethodHandles.Lookup privateLookup; try { privateLookup = MethodHandles.privateLookupIn(clazz, LOOKUP); } catch (IllegalAccessException e) { GTCEu.LOGGER.error("Sync: Failed to create method handle lookup for class {}", clazz); - GTCEu.LOGGER.error(e.getMessage()); - return; + throw e; } Map> changeListeners = new HashMap<>(); @@ -94,8 +103,7 @@ private ClassSyncData(Class clazz) { } catch (IllegalAccessException e) { GTCEu.LOGGER.error("Sync: Failed to acquire variable handle for field {} {}", field.getName(), clazz.getName()); - GTCEu.LOGGER.error(e.getMessage()); - continue; + throw e; } FieldSyncData syncData = new FieldSyncData(field, handle, ValueTransformers.get(field.getGenericType()), @@ -106,7 +114,7 @@ private ClassSyncData(Class clazz) { } Class parent = clazz.getSuperclass(); - if (parent != Object.class) { + if (ISyncManaged.class.isAssignableFrom(parent) || ISyncAnnotated.class.isAssignableFrom(parent)) { ClassSyncData parentHandles = CACHE.get(parent); managedFields.addAll(parentHandles.managedFields); clientSyncFields.addAll(parentHandles.clientSyncFields); diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/FieldSyncData.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/FieldSyncData.java index 9561161a8b6..ec9265cb3a0 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/sync_system/FieldSyncData.java +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/FieldSyncData.java @@ -5,6 +5,7 @@ import com.gregtechceu.gtceu.api.sync_system.data_transformers.ValueTransformer; import lombok.Setter; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.lang.invoke.MethodHandle; @@ -15,11 +16,12 @@ /** * Information about the sync behaviour of fields with sync annotations in ISyncManaged classes */ +@ApiStatus.Internal public final class FieldSyncData { public final String fieldName, nbtSaveKey; public final VarHandle handle; - public final boolean triggerClientRerender, isSyncManaged; + public final boolean triggerClientRerender; @Setter public @Nullable ValueTransformer transformer; public final List changeListenerHandles; @@ -30,7 +32,6 @@ public FieldSyncData(Field field, VarHandle handle, @Nullable ValueTransformer) field.transformer).serializeNBT(currentValue, + new ValueTransformer.TransformerContext<>(holder, field.type, currentValue, field.fieldName, + writeClientFields, fullSync)); + + } catch (Exception e) { + GTCEu.LOGGER.error("Sync: Failed to serialize field {}", field.fieldName, e); + } + + return new CompoundTag(); + } + + @SuppressWarnings("unchecked") + public static void deserializeField(Object holder, FieldSyncData field, + @Nullable Tag savedValue, + boolean readingClientFields) { + if (savedValue == null || savedValue instanceof CompoundTag compound && compound.isEmpty()) return; + + if (savedValue instanceof CompoundTag compound && compound.getBoolean("null")) { + field.handle.set(holder, null); + return; + } + + if (field.transformer == null) { + field.setTransformer(ValueTransformers.get(field.type.getRawType())); + if (field.transformer == null) { + GTCEu.LOGGER.error("Sync: Failed to deserialize field {} in class {}: Missing value transformer for {}", + field.fieldName, holder.getClass().getName(), field.type); + return; + } + } + + try { + ValueTransformer transformer = (ValueTransformer) field.transformer; + var current = field.handle.get(holder); + + Object result = transformer.deserializeNBT(savedValue, new ValueTransformer.TransformerContext<>( + holder, field.type, current, field.fieldName, readingClientFields, false)); + + if (result != current) { + field.handle.set(holder, result); + } + + } catch (Exception e) { + if (e instanceof UnsupportedOperationException) { + GTCEu.LOGGER.error( + "Sync: failed to perform VarHandle set: unsupported op on {} (you are probably trying to sync a final field)", + field.fieldName); + return; + } + GTCEu.LOGGER.error("Sync: Failed to deserialize field {}", field.fieldName, e); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/ISyncAnnotated.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/ISyncAnnotated.java new file mode 100644 index 00000000000..9991a4f1626 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/ISyncAnnotated.java @@ -0,0 +1,30 @@ +package com.gregtechceu.gtceu.api.sync_system; + +import com.gregtechceu.gtceu.api.sync_system.annotations.ClientFieldChangeListener; +import com.gregtechceu.gtceu.api.sync_system.data_transformers.ValueTransformer; + +import net.minecraftforge.common.util.INBTSerializable; + +/** + * Represents a class with fields that have sync annotations.
+ * + * This interface has no functionality on its own, it just marks that a class has sync annotations.
+ * + * A sync annotated class cannot be synced on its own, it must be a field of an {@link ISyncManaged} class.
+ * + * All {@link ISyncAnnotated} classes should have a no-args constructor.
+ * + * {@link ClientFieldChangeListener} does not work for fields in {@link ISyncAnnotated} classes. + * + *

+ * A field of type {@code T} can be marked with sync annotations if: + *

    + *
  • {@code T} is primitive + *
  • {@code T} has an {@link ValueTransformer} registered + *
  • {@code T} implements {@link INBTSerializable} + *
  • {@code T} is an {@link ISyncAnnotated} class + *
+ * + * @see ISyncManaged + */ +public interface ISyncAnnotated {} diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/ISyncManaged.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/ISyncManaged.java index ec48892047d..18907a69ac9 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/sync_system/ISyncManaged.java +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/ISyncManaged.java @@ -2,33 +2,51 @@ import com.gregtechceu.gtceu.api.sync_system.data_transformers.ValueTransformer; -import net.minecraft.nbt.Tag; import net.minecraftforge.common.util.INBTSerializable; +import org.jetbrains.annotations.Nullable; + /** - * Represents a class with fields that have sync annotations. + * Represents a class with fields that have sync annotations.
+ * Differs from {@link ISyncAnnotated} in that more control is provided over syncing.
+ * An {@link ISyncManaged} class manages the sync status of itself and its fields, + * while a {@link ISyncAnnotated} must be managed by a field in an {@link ISyncManaged} class. *

* A field of type {@code T} can be marked with sync annotations if: *

    *
  • {@code T} is primitive *
  • {@code T} has an {@link ValueTransformer} registered - *
  • {@code T} implements {@link INBTSerializable} - *
  • {@code T} is a {@link ISyncManaged} class + *
  • {@code T} implements {@link INBTSerializable} + *
  • {@code T} is an {@link ISyncManaged} or {@link ISyncAnnotated} class *
* * @see SyncDataHolder + * @see ISyncAnnotated */ public interface ISyncManaged { SyncDataHolder getSyncDataHolder(); + /** + * Gets the parent sync object of this sync object + * + * @return The parent sync object, can only return null if this object does not have a parent sync object and both + * {@link #scheduleRenderUpdate()} and {@link #markAsChanged()} are overriden + */ + @Nullable + ISyncManaged getParentSyncObject(); + /** * Function called when a synced field requests a rerender */ - void scheduleRenderUpdate(); + default void scheduleRenderUpdate() { + if (getParentSyncObject() != null) getParentSyncObject().scheduleRenderUpdate(); + } /** * Function called to notify the server that this object has been updated and must be synced to clients */ - void markAsChanged(); + default void markAsChanged() { + if (getParentSyncObject() != null) getParentSyncObject().markAsChanged(); + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/ManagedSyncBlockEntity.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/ManagedSyncBlockEntity.java index c6bab08d64c..35e623fe26a 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/sync_system/ManagedSyncBlockEntity.java +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/ManagedSyncBlockEntity.java @@ -1,10 +1,10 @@ package com.gregtechceu.gtceu.api.sync_system; -import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.blockentity.BlockEntityCreationInfo; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.Connection; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; @@ -51,16 +51,32 @@ protected final void saveAdditional(CompoundTag tag) { } /** - * Loads BE data from world save or from a client update packet. - * Override this to add logic for modifying saved data before it is loaded (e.g. for cross-version compatibility). - * When overriding, {@code super.load(tag)} must be called AFTER any custom logic. + * Loads BE data from world save.
+ * Override this to add logic for modifying saved data before it is loaded (e.g. for cross-version + * compatibility).
+ * When overriding, {@code super.load(tag)} must be called AFTER any custom logic. + * + * @param tag The tag to load */ @Override @MustBeInvokedByOverriders public void load(CompoundTag tag) { super.load(tag); - getSyncDataHolder().deserializeNBT(tag, - (getLevel() == null ? GTCEu.isClientThread() : getLevel().isClientSide)); + getSyncDataHolder().deserializeNBT(tag, false); + } + + /** + * Loads BE data from client update packet + */ + @MustBeInvokedByOverriders + public void clientLoad(CompoundTag tag) { + getSyncDataHolder().deserializeNBT(tag, true); + } + + @Override + public final void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt) { + var compound = pkt.getTag(); + if (compound != null) clientLoad(compound); } /** @@ -82,12 +98,25 @@ public CompoundTag getUpdateTag() { return ClientboundBlockEntityDataPacket.create(this, b -> getSyncDataHolder().serializeNBT(true)); } + @Override + public @Nullable ISyncManaged getParentSyncObject() { + return null; + } + @Override public final void markAsChanged() { isDirty = true; } - public final void updateTick() { + @Override + public void setChanged() { + if (getLevel() != null) { + getLevel().blockEntityChanged(getBlockPos()); + } + } + + @MustBeInvokedByOverriders + public void serverTick() { setChanged(); if (isDirty) { Objects.requireNonNull(getLevel()).sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/SyncDataHolder.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/SyncDataHolder.java index c0aff5655e7..8d216612a78 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/sync_system/SyncDataHolder.java +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/SyncDataHolder.java @@ -59,7 +59,7 @@ public CompoundTag serializeNBT(boolean writeClientFields, boolean fullSync) { CompoundTag tag = new CompoundTag(); for (var field : fieldsToSerialize) { if (shouldSerializeField(field, writeClientFields, fullSync)) { - Tag nbtValue = serializeField(holder, field, writeClientFields, fullSync); + Tag nbtValue = FieldSyncHandler.serializeField(holder, field, writeClientFields, fullSync); tag.put(field.nbtSaveKey, nbtValue); } } @@ -67,7 +67,8 @@ public CompoundTag serializeNBT(boolean writeClientFields, boolean fullSync) { } private boolean shouldSerializeField(FieldSyncData field, boolean writeClient, boolean fullSync) { - return !writeClient || fullSync || dirtySyncFields.contains(field.fieldName) || field.isSyncManaged; + return !writeClient || fullSync || dirtySyncFields.contains(field.fieldName) || + (field.type.getClassValue() != null && ISyncManaged.class.isAssignableFrom(field.type.getClassValue())); } public void deserializeNBT(CompoundTag tag, boolean readingClientFields) { @@ -77,7 +78,7 @@ public void deserializeNBT(CompoundTag tag, boolean readingClientFields) { for (var field : fieldsToCheck) { Tag savedValue = tag.get(field.nbtSaveKey); - deserializeField(holder, field, savedValue, readingClientFields); + FieldSyncHandler.deserializeField(holder, field, savedValue, readingClientFields); if (readingClientFields) { try { @@ -90,8 +91,7 @@ public void deserializeNBT(CompoundTag tag, boolean readingClientFields) { "Invalid method signature for change listener for field %s %s" .formatted(field.fieldName, holder.getClass().getName())); } - GTCEu.LOGGER.error("Sync: Error while invoking change listener for field {}", field.fieldName); - GTCEu.LOGGER.error(e); + GTCEu.LOGGER.error("Sync: Error while invoking change listener for field {}", field.fieldName, e); } if (field.triggerClientRerender) holder.scheduleRenderUpdate(); @@ -99,83 +99,6 @@ public void deserializeNBT(CompoundTag tag, boolean readingClientFields) { } } - @SuppressWarnings("unchecked") - private static Tag serializeField(ISyncManaged holder, FieldSyncData field, - boolean writeClientFields, boolean fullSync) { - Object currentValue = field.handle.get(holder); - - if (!field.isSyncManaged && currentValue == null) { - var nullCompound = new CompoundTag(); - nullCompound.putBoolean("null", true); - return nullCompound; - } - - try { - - if (field.transformer != null) { - return ((ValueTransformer) field.transformer).serializeNBT(currentValue, - new ValueTransformer.TransformerContext<>(holder, field.type, currentValue, field.fieldName, - writeClientFields, fullSync)); - } else if (currentValue instanceof ISyncManaged syncObj) { - return syncObj.getSyncDataHolder().serializeNBT(writeClientFields, fullSync); - } else { - GTCEu.LOGGER.error("Sync: Failed to serialize field {} in class {}: Missing value transformer for {}", - field.fieldName, holder.getClass().getName(), field.type); - } - - } catch (Exception e) { - GTCEu.LOGGER.error("Sync: Failed to serialize field {}", field.fieldName); - GTCEu.LOGGER.error(e); - } - - return new CompoundTag(); - } - - @SuppressWarnings("unchecked") - private static void deserializeField(ISyncManaged holder, FieldSyncData field, - @Nullable Tag savedValue, - boolean readingClientFields) { - Object currentVal = field.handle.get(holder); - - if (savedValue == null || savedValue instanceof CompoundTag compound && compound.isEmpty()) return; - - if (savedValue instanceof CompoundTag compound && compound.getBoolean("null")) { - field.handle.set(holder, null); - return; - } - - try { - if (field.transformer != null) { - ValueTransformer transformer = (ValueTransformer) field.transformer; - try { - var current = field.handle.get(holder); - Object result = transformer.deserializeNBT(savedValue, new ValueTransformer.TransformerContext<>( - holder, field.type, current, field.fieldName, readingClientFields, false)); - if (result != current) { - field.handle.set(holder, result); - } - } catch (UnsupportedOperationException e) { - GTCEu.LOGGER.error("Sync: failed to perform VarHandle set: unsupported op {} {}", - field.fieldName, field.handle.toString()); - } - } else if (field.isSyncManaged && savedValue instanceof CompoundTag compound) { - if (currentVal == null) { - GTCEu.LOGGER.error("Sync: ISyncManaged field was null, cannot instantiate {}", - field.fieldName); - return; - } - if (currentVal instanceof ISyncManaged syncObj) - syncObj.getSyncDataHolder().deserializeNBT(compound, readingClientFields); - } else { - GTCEu.LOGGER.error("Sync: Failed to deserialize field {} in class {}: Missing value transformer for {}", - field.fieldName, holder.getClass().getName(), field.type); - } - } catch (Exception e) { - GTCEu.LOGGER.error("Sync: Failed to deserialize field {}", field.fieldName); - GTCEu.LOGGER.error(e); - } - } - public static class SyncManagedTransformer implements ValueTransformer { @Override @@ -186,8 +109,15 @@ public Tag serializeNBT(ISyncManaged value, TransformerContext con @Override public @Nullable ISyncManaged deserializeNBT(Tag tag, TransformerContext context) { ISyncManaged syncManaged = context.currentValue(); - Objects.requireNonNull(syncManaged).getSyncDataHolder().deserializeNBT((CompoundTag) tag, - context.isClientSync()); + + if (syncManaged == null) { + GTCEu.LOGGER.error("Sync: ISyncManaged field was null, cannot instantiate {}", + context.fieldName()); + return null; + } + + syncManaged.getSyncDataHolder().deserializeNBT((CompoundTag) tag, context.isClientSync()); + return syncManaged; } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/TypeDeclaration.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/TypeDeclaration.java index d90440ad76e..298cca8c9b4 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/sync_system/TypeDeclaration.java +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/TypeDeclaration.java @@ -1,13 +1,16 @@ package com.gregtechceu.gtceu.api.sync_system; import lombok.Getter; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; import java.util.Arrays; +@ApiStatus.Internal public class TypeDeclaration { @Getter @@ -30,6 +33,10 @@ public TypeDeclaration(Type type) { this.classValue = null; this.arrayComponentType = new TypeDeclaration(genericArrayType.getGenericComponentType()); this.genericTypeArgs = new TypeDeclaration[0]; + } else if (type instanceof WildcardType) { + this.classValue = null; + this.genericTypeArgs = new TypeDeclaration[0]; + this.arrayComponentType = null; } else { this.classValue = (Class) type; this.genericTypeArgs = new TypeDeclaration[0]; diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/SimpleClassTransformer.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/SimpleClassTransformer.java index 83c85f14989..9119194665e 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/SimpleClassTransformer.java +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/SimpleClassTransformer.java @@ -2,8 +2,6 @@ import net.minecraft.nbt.Tag; -import org.jetbrains.annotations.NotNull; - import java.util.function.Function; public class SimpleClassTransformer implements ValueTransformer { @@ -21,7 +19,7 @@ public SimpleClassTransformer(Function write, } @Override - public @NotNull Tag serializeNBT(T value, ValueTransformer.TransformerContext context) { + public Tag serializeNBT(T value, ValueTransformer.TransformerContext context) { return write.apply(value); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/SyncAnnotatedTransformer.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/SyncAnnotatedTransformer.java new file mode 100644 index 00000000000..eb4c93225f1 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/SyncAnnotatedTransformer.java @@ -0,0 +1,57 @@ +package com.gregtechceu.gtceu.api.sync_system.data_transformers; + +import com.gregtechceu.gtceu.api.sync_system.ClassSyncData; +import com.gregtechceu.gtceu.api.sync_system.FieldSyncData; +import com.gregtechceu.gtceu.api.sync_system.FieldSyncHandler; +import com.gregtechceu.gtceu.api.sync_system.ISyncAnnotated; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; + +import lombok.SneakyThrows; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Set; + +public class SyncAnnotatedTransformer implements ValueTransformer { + + @Override + public Tag serializeNBT(ISyncAnnotated value, TransformerContext context) { + var syncData = ClassSyncData.getClassData(value.getClass()); + + Set fieldsToSerialize = context.isClientSync() ? syncData.getClientSyncFields() : + syncData.getServerSaveFields(); + + CompoundTag tag = new CompoundTag(); + for (var field : fieldsToSerialize) { + Tag nbtValue = FieldSyncHandler.serializeField(context.holder(), field, context.isClientSync(), true); + tag.put(field.nbtSaveKey, nbtValue); + } + + return tag; + } + + @Override + @SneakyThrows + public @Nullable ISyncAnnotated deserializeNBT(Tag tag, TransformerContext context) { + if (!(tag instanceof CompoundTag compound)) return null; + + var typeData = Objects.requireNonNull(context.type().getClassValue()); + + var syncData = ClassSyncData.getClassData(typeData); + + Set fieldsToCheck = context.isClientSync() ? syncData.getClientSyncFields() : + syncData.getServerSaveFields(); + + var holder = context.currentValue(); + if (holder == null) holder = (ISyncAnnotated) typeData.getConstructor().newInstance(); + + for (var field : fieldsToCheck) { + Tag savedValue = compound.get(field.nbtSaveKey); + FieldSyncHandler.deserializeField(holder, field, savedValue, context.isClientSync()); + } + + return holder; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/ValueTransformer.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/ValueTransformer.java index 2442e2362ee..fd31745151c 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/ValueTransformer.java +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/ValueTransformer.java @@ -1,6 +1,5 @@ package com.gregtechceu.gtceu.api.sync_system.data_transformers; -import com.gregtechceu.gtceu.api.sync_system.ISyncManaged; import com.gregtechceu.gtceu.api.sync_system.TypeDeclaration; import net.minecraft.nbt.Tag; @@ -18,7 +17,7 @@ public interface ValueTransformer { /** * A record holding information about the context from which this value transformer is currently being invoked. * - * @param holder The sync object which holds the specific field being serialized by this transformer. + * @param holder The object which holds the specific field being serialized by this transformer. * @param type An object describing the type of the field currently being serialized/deserialized. * @param currentValue The current value (if any) of the field currently being serialized/deserialized. * @param fieldName The name of the field being serialized, or a string denoting the current sync context if not @@ -27,7 +26,7 @@ public interface ValueTransformer { * being written to the server save. * */ - record TransformerContext(ISyncManaged holder, TypeDeclaration type, + record TransformerContext(Object holder, TypeDeclaration type, @Nullable U currentValue, @Nullable String fieldName, boolean isClientSync, boolean isClientFullSyncUpdate) {} diff --git a/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/ValueTransformers.java b/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/ValueTransformers.java index 0f1b5a75b6c..06f65659147 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/ValueTransformers.java +++ b/src/main/java/com/gregtechceu/gtceu/api/sync_system/data_transformers/ValueTransformers.java @@ -6,6 +6,7 @@ import com.gregtechceu.gtceu.api.recipe.GTRecipe; import com.gregtechceu.gtceu.api.recipe.GTRecipeType; import com.gregtechceu.gtceu.api.registry.GTRegistries; +import com.gregtechceu.gtceu.api.sync_system.ISyncAnnotated; import com.gregtechceu.gtceu.api.sync_system.ISyncManaged; import com.gregtechceu.gtceu.api.sync_system.SyncDataHolder; import com.gregtechceu.gtceu.api.sync_system.TypeDeclaration; @@ -182,6 +183,7 @@ public static void registerTransformerSupplier(Class type, Supplier { this.updateBatterySubscription(); this.updateBreakerSubscription(); diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/electric/HullMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/electric/HullMachine.java index bf6e4288433..c2fef181b85 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/electric/HullMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/electric/HullMachine.java @@ -15,8 +15,6 @@ import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import brachy.modularui.api.drawable.IDrawable; import brachy.modularui.drawable.GuiTextures; @@ -51,9 +49,8 @@ public HullMachine(BlockEntityCreationInfo info, int tier) { @Override public void onLoad() { super.onLoad(); - if (GTCEu.Mods.isAE2Loaded() && gridNodeHost instanceof GridNodeHostTrait connectedBlockEntity && - getLevel() instanceof ServerLevel level) { - level.getServer().tell(new TickTask(0, connectedBlockEntity::init)); + if (GTCEu.Mods.isAE2Loaded() && gridNodeHost instanceof GridNodeHostTrait connectedBlockEntity) { + scheduleForNextServerTick(connectedBlockEntity::init); } } diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/electric/ItemCollectorMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/electric/ItemCollectorMachine.java index 6d45b35e080..bd14bf21e00 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/electric/ItemCollectorMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/electric/ItemCollectorMachine.java @@ -24,8 +24,6 @@ import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; @@ -145,10 +143,7 @@ public void onLoad() { super.onLoad(); if (isRemote()) return; - if (getLevel() instanceof ServerLevel serverLevel) { - - serverLevel.getServer().tell(new TickTask(0, this::updateCollectionSubscription)); - } + scheduleForNextServerTick(this::updateCollectionSubscription); energySubs = energyContainer.addChangedListener(() -> { this.updateBatterySubscription(); diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/research/DataBankMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/research/DataBankMachine.java index cc7bbaf8341..bc007168eaf 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/research/DataBankMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/research/DataBankMachine.java @@ -19,8 +19,6 @@ import net.minecraft.ChatFormatting; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.network.chat.Component; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; import brachy.modularui.api.widget.IWidget; @@ -86,10 +84,7 @@ public void onStructureFormed() { onStructureInvalid(); return; } - - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateTickSubscription)); - } + updateTickSubscription(); } protected int calculateEnergyUsage() { @@ -124,9 +119,7 @@ public void onStructureInvalid() { @Override public void onLoad() { super.onLoad(); - if (this.isFormed() && getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateTickSubscription)); - } + scheduleForNextServerTick(this::updateTickSubscription); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/research/HPCAMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/research/HPCAMachine.java index 63fb624d87a..0c74a752f7d 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/research/HPCAMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/research/HPCAMachine.java @@ -37,8 +37,6 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; @@ -130,17 +128,13 @@ public void onStructureFormed() { this.coolantHandler = new FluidHandlerList(coolantContainers); this.hpcaHandler.onStructureForm(componentTraits); - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateTickSubscription)); - } + scheduleForNextServerTick(this::updateTickSubscription); } @Override public void onLoad() { super.onLoad(); - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateTickSubscription)); - } + scheduleForNextServerTick(this::updateTickSubscription); } @Override @@ -435,6 +429,11 @@ public HPCAGridHandler(@Nullable HPCAMachine controller) { this.controller = controller; } + @Override + public @Nullable ISyncManaged getParentSyncObject() { + return controller; + } + public void onStructureForm(Collection components) { reset(); for (var component : components) { @@ -792,15 +791,5 @@ public void tryGatherClientComponents(Level world, BlockPos pos, Direction front public void clearClientComponents() { components.clear(); } - - @Override - public void markAsChanged() { - controller.markAsChanged(); - } - - @Override - public void scheduleRenderUpdate() { - controller.scheduleRenderUpdate(); - } } } diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/part/FluidHatchPartMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/part/FluidHatchPartMachine.java index a2a671f050a..c9f6b21caaa 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/part/FluidHatchPartMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/part/FluidHatchPartMachine.java @@ -28,8 +28,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; @@ -110,9 +108,7 @@ public static int getTankCapacity(int initialCapacity, int tier) { @Override public void onLoad() { super.onLoad(); - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateTankSubscription)); - } + scheduleForNextServerTick(this::updateTankSubscription); getHandlerList().setColor(getPaintingColor()); tankSubs = tank.addChangedListener(this::updateTankSubscription); } diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/part/ItemBusPartMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/part/ItemBusPartMachine.java index 406231b8665..4e1dee83d4a 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/part/ItemBusPartMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/part/ItemBusPartMachine.java @@ -26,8 +26,6 @@ import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; @@ -121,9 +119,7 @@ protected NotifiableItemStackHandler createCircuitItemHandler(IO io) { @Override public void onLoad() { super.onLoad(); - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateInventorySubscription)); - } + scheduleForNextServerTick(this::updateInventorySubscription); getHandlerList().setDistinct(isDistinct); getHandlerList().setColor(getPaintingColor()); inventorySubs = getInventory().addChangedListener(this::updateInventorySubscription); diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/steam/LargeBoilerMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/steam/LargeBoilerMachine.java index c43ddeb7750..39d66b05771 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/steam/LargeBoilerMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/steam/LargeBoilerMachine.java @@ -23,8 +23,6 @@ import net.minecraft.ChatFormatting; import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; import net.minecraft.world.level.material.Fluids; @@ -82,17 +80,13 @@ public LargeBoilerMachine.LargeBoilerRecipeLogic getRecipeLogic() { @Override public void onStructureFormed() { super.onStructureFormed(); - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateSteamSubscription)); - } + updateSteamSubscription(); } @Override public void onStructureInvalid() { super.onStructureInvalid(); - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateSteamSubscription)); - } + updateSteamSubscription(); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/steam/SteamMinerMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/steam/SteamMinerMachine.java index c049558e4fc..9719f9cd97f 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/steam/SteamMinerMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/steam/SteamMinerMachine.java @@ -24,8 +24,6 @@ import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; import net.minecraftforge.fluids.capability.IFluidHandler; @@ -102,10 +100,8 @@ public void onNeighborChanged(Block block, BlockPos fromPos, boolean isMoving) { @Override public void onLoad() { super.onLoad(); + scheduleForNextServerTick(this::updateAutoOutputSubscription); if (!isRemote()) { - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateAutoOutputSubscription)); - } exportItemSubs = exportItems.addChangedListener(this::updateAutoOutputSubscription); } } diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/storage/LongDistanceEndpointMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/storage/LongDistanceEndpointMachine.java index aeb5c58dc3e..507a8a19ba2 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/storage/LongDistanceEndpointMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/storage/LongDistanceEndpointMachine.java @@ -16,8 +16,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; import lombok.Getter; @@ -105,9 +103,7 @@ public void setFrontFacing(Direction frontFacing) { @Override public void onLoad() { super.onLoad(); - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateRefreshNetSubscription)); - } + scheduleForNextServerTick(this::updateRefreshNetSubscription); } @Override @@ -203,9 +199,7 @@ private List findNetworks() { public void invalidateLink() { if (link != null) { this.link = null; - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateRefreshNetSubscription)); - } + scheduleForNextServerTick(this::updateRefreshNetSubscription); } } diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/trait/AutoOutputTrait.java b/src/main/java/com/gregtechceu/gtceu/common/machine/trait/AutoOutputTrait.java index 12670ed989f..a5ab7a3bbf6 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/trait/AutoOutputTrait.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/trait/AutoOutputTrait.java @@ -21,8 +21,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -133,10 +131,8 @@ public void onMachineLoad() { Direction.UP; this.fluidOutputDirection = itemOutputDirection; - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::updateFluidOutputSubscription)); - serverLevel.getServer().tell(new TickTask(0, this::updateItemOutputSubscription)); - } + getMachine().scheduleForNextServerTick(this::updateFluidOutputSubscription); + getMachine().scheduleForNextServerTick(this::updateItemOutputSubscription); for (var handler : itemHandlers) { if (handler instanceof NotifiableItemStackHandler notifiable) itemSubs.add(notifiable.addChangedListener(this::updateItemOutputSubscription)); diff --git a/src/main/java/com/gregtechceu/gtceu/common/mui/GTMuiWidgets.java b/src/main/java/com/gregtechceu/gtceu/common/mui/GTMuiWidgets.java index 6072a343e2c..b4e93736ffe 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/mui/GTMuiWidgets.java +++ b/src/main/java/com/gregtechceu/gtceu/common/mui/GTMuiWidgets.java @@ -149,21 +149,6 @@ public static ToggleButton createPowerButton(IControllable workable) { "behaviour.soft_hammer"); } - /* - * public static ProgressWidget createProgressBar(IRecipeLogicMachine workableMachine, PanelSyncManager syncManager, - * UITexture texture, int size) { - * DoubleSyncValue progressPercent = syncManager.getOrCreateSyncHandler("progressPercent", DoubleSyncValue.class, - * () -> new DoubleSyncValue(() -> { - * if (workableMachine.getMaxProgress() == 0.0f) return 0.0f; - * return workableMachine.getProgress() / (double) workableMachine.getMaxProgress(); - * })); - * - * return new ProgressWidget() - * .texture(texture, size) - * .value(progressPercent); - * } - */ - public static FluidSlot createTankWidget() { return new FluidSlot().size(20, 58).alwaysShowFull(false); } diff --git a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/feature/multiblock/IMEStockingPart.java b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/feature/multiblock/IMEStockingPart.java index 21c98e18cf8..36e5735fed1 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/feature/multiblock/IMEStockingPart.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/feature/multiblock/IMEStockingPart.java @@ -8,9 +8,6 @@ import com.gregtechceu.gtceu.common.mui.widgets.PopupPanel; import com.gregtechceu.gtceu.integration.ae2.slot.IConfigurableSlotList; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; - import appeng.api.stacks.GenericStack; import brachy.modularui.api.IPanelHandler; import brachy.modularui.api.drawable.Text; @@ -35,11 +32,9 @@ default void addedToController(MultiblockControllerMachine controller) { // that we have in our own bus. setAutoPullTest(stack -> !this.testConfiguredInOtherPart(stack)); // also ensure that our current config is valid given other inputs - if (self().getLevel() instanceof ServerLevel serverLevel) { - // wait for 1 tick - // we should not access the part list at this time - serverLevel.getServer().tell(new TickTask(0, this::validateConfig)); - } + // wait for 1 tick + // we should not access the part list at this time + controller.scheduleForNextServerTick(this::validateConfig); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/trait/GridNodeHolder.java b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/trait/GridNodeHolder.java index 3464cd4958a..a526240fb05 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/trait/GridNodeHolder.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/trait/GridNodeHolder.java @@ -8,8 +8,6 @@ import com.gregtechceu.gtceu.integration.ae2.utils.SerializableManagedGridNode; import net.minecraft.core.Direction; -import net.minecraft.server.TickTask; -import net.minecraft.server.level.ServerLevel; import appeng.api.networking.GridFlags; import appeng.me.helpers.BlockEntityNodeListener; @@ -59,9 +57,7 @@ protected void createMainNode() { @Override public void onMachineLoad() { super.onMachineLoad(); - if (getLevel() instanceof ServerLevel serverLevel) { - serverLevel.getServer().tell(new TickTask(0, this::createMainNode)); - } + getMachine().scheduleForNextServerTick(this::createMainNode); } @Override