Jump to content

Data synchronization issue between client and server


Huntwer

Recommended Posts

My goal is to create a block. Right-clicking on the block opens a GUI, a simple window that contains an EditBox and a button. When I press the button, the value of the EditBox should be saved persistently, so that in every gaming session, it is retrieved and automatically inserted back into the window when I right-click again.

The GUI is implemented with the following code:

public class PadGui extends Screen {
...
    public PadGui(String title, BlockState padblockState, String stage, BlockEntity blockEntity) {
        super(Component.literal(title));
        this.title = title;
        this.padBlockState = padblockState;
        this.stage = stage;
        this.blockEntity = (PadBlockEntity) blockEntity;
    }

  	@Override
    protected void init() {
      super.init();
      ...
      LabelWidget passwordLabel = new LabelWidget(this.width / 2 - widthRef / 2, heightRef - 10, "Password", true);
      this.inputPassword = new EditBox(this.font, this.width / 2 - widthRef / 2, heightRef, widthRef, 20, Component.literal("Password"));
      Button.Builder confirmInsertBtnBuilder = new Button.Builder(Component.literal("Save Password"), button -> this.onSavePassword(inputPassword.getValue()));
      confirmInsertBtnBuilder.width(widthRef);
      confirmInsertBtnBuilder.pos(this.width / 2 - widthRef / 2, heightRef + 65);
      confirmInsertButton = confirmInsertBtnBuilder.build();
      confirmInsertButton.active = false;

      addRenderableWidget(passwordLabel);
      addRenderableWidget(inputPassword);
      addRenderableWidget(confirmInsertButton);
	}

    private void onSavePassword(String password) {
      this.blockEntity.setPassword(password);
      onClose();
    }

    @Override
      public void onClose() {
        PadBlockPacket padBlockPacket = new PadBlockPacket(this.blockEntity.getPos(), this.blockEntity.getSupportBlockPos(), this.blockEntity.getPassword());
        PadLockDoorMod.INSTANCE.sendToServer(padBlockPacket);
        Minecraft.getInstance().setScreen(null);
    }
}

This is code for BlockEntity:
 

public class PadBlockEntity extends BlockEntity {
    private BlockPos pos;
    private BlockPos supportBlockPos;
    private String password;
  
    public PadBlockEntity(BlockPos pos, BlockState state) {
        super(PadLockDoorMod.PAD_BLOCK_ENTITY.get(), pos, state);
        this.pos = pos;
    }

    public BlockPos getPos() {
        return pos;
    }

    public void setPos(BlockPos pos) {
        this.pos = pos;
    }

    public BlockPos getSupportBlockPos() {
        return supportBlockPos;
    }

    public void setSupportBlockPos(BlockPos supportBlockpos) {
        this.supportBlockPos = supportBlockpos;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    protected void saveAdditional(CompoundTag nbt) {
        super.saveAdditional(nbt);
        nbt.putString("password", this.getPassword());
        nbt.putLong("pos", this.getPos().asLong());
        nbt.putLong("supportBlockPos", this.getSupportBlockPos().asLong());
    }

    @Override
    public void load(CompoundTag nbt) {
        super.load(nbt);
        this.password = nbt.getString("password");
        this.pos = BlockPos.of(nbt.getLong("pos"));
        this.supportBlockPos = BlockPos.of(nbt.getLong("supportBlockPos"));
    }

    @Override
    public CompoundTag getUpdateTag() {
        super.getUpdateTag();
        return saveWithoutMetadata();
    }

    @Override
    public void handleUpdateTag(CompoundTag nbt) {
        super.handleUpdateTag(nbt);
        this.load(nbt);
    }
}

From this code, my doubts begin. If I understand correctly, getUpdateTag is called by the server, and handleUpdateTag is called by the client to synchronize data from the server to the client, right?

Then I read from here https://forge.gemwire.uk/wiki/SimpleChannel that to synchronize data from the client to the server, I need to send a data packet to the server using SimpleChannel. So, I created a class representing the message, a class representing the packet, and then registered the SimpleChannel with the message in the mod's main class.

This is the class representing the packet. It contains the encode and decode methods for the packet data:
 

public class PadBlockPacket {
    private BlockPos pos;
    private BlockPos supportBlockPos;
    private final String password;

    public PadBlockPacket(BlockPos pos, BlockPos supportBlockPos, String password) {
        this.pos = pos;
        this.supportBlockPos = supportBlockPos;
        this.password = password;
    }
    public void encode(FriendlyByteBuf friendlyByteBuf) {
        friendlyByteBuf.writeBlockPos(this.supportBlockPos);
        friendlyByteBuf.writeBlockPos(this.pos);
        friendlyByteBuf.writeUtf(this.password);
    }

    public static PadBlockPacket decode(FriendlyByteBuf buffer) {
        BlockPos supportBlockPos = buffer.readBlockPos();
        BlockPos pos = buffer.readBlockPos();
        String password = buffer.readUtf(32767);
        return new PadBlockPacket(pos, supportBlockPos, password);
    }

    public BlockPos getPos() {
        return pos;
    }

    public BlockPos getSupportBlockPos() {
        return supportBlockPos;
    }

    public String getPassword() {
        return password;
    }
}

This is the packet handler:
 

public class PadLockPacketServerHandler {
    public static void handle(PadBlockPacket padBlockPacket, Supplier<NetworkEvent.Context> ctx) {
        ctx.get().enqueueWork(() -> {
            ServerPlayer player = ctx.get().getSender();
            Level level = player.level;
            BlockPos pos = padBlockPacket.getPos();
            BlockPos supportBlockPos = padBlockPacket.getSupportBlockPos();
            String password = padBlockPacket.getPassword();
            PadBlockEntity blockEntity = (PadBlockEntity) level.getBlockEntity(pos);
            blockEntity.setPos(pos);
            blockEntity.setSupportBlockPos(supportBlockPos);
            blockEntity.setPassword(password);
        });
        ctx.get().setPacketHandled(true);
    }
}

I assume that the encode method is called by the client, and the decode and handle methods are called by the server when it receives data from the packet through SimpleChannel, right?

Finally, this is how I register the SimpleChannel and the message in the main class of the mod:
 

public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel(
  new ResourceLocation(MODID, "main"),
  () -> PROTOCOL_VERSION,
  PROTOCOL_VERSION::equals,
  PROTOCOL_VERSION::equals
);

public PadLockDoorMod() {
    IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();

    ...
    INSTANCE.registerMessage(0, PadBlockPacket.class, PadBlockPacket::encode, PadBlockPacket::decode, PadLockPacketServerHandler::handle);
}


However, this doesn't work; currently, the client and server data aren't synchronized. It seems that when clicking the button in the GUI, the BlockEntity isn't updated on the server thread. So, I have two questions:

  1. Is the approach for persisting block information and the GUI correct?
  2. In the tutorial at the link above, it's also mentioned to use a packet handler for client-server data, showing this code:
    // In Packet class
    public static void handle(MyClientMessage msg, Supplier<NetworkEvent.Context> ctx) {
        ctx.get().enqueueWork(() ->
            // Make sure it's only executed on the physical client
            DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClientPacketHandlerClass.handlePacket(msg, ctx))
        );
        ctx.get().setPacketHandled(true);
    }
    
    // In ClientPacketHandlerClass
    public static void handlePacket(MyClientMessage msg, Supplier<NetworkEvent.Context> ctx) {
        // Do stuff
    }

    This confuses me because I had previously understood that to synchronize data from the server to the client, it was sufficient to implement the getUpdateTag and handleUpdateTag methods on the BlockEntity and not use data packets in this case. In that case, how should the handlePacket method be implemented in my situation?

 

I would like to know if my current understanding of how data synchronization between the client and server works is correct or incorrect and incomplete. Thank you for your help.

Link to comment
Share on other sites

I managed to solve it.

I'm not sure why exactly, but in my class that extends EntityBlock, if I implement the newBlockEntity method like this:

@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
  return new PadBlockEntity(pos, state);
}

it doesn't work. But it does work if I do it this way:

@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
	return PadLockDoorMod.PAD_BLOCK_ENTITY.get().create(pos, state);
}

I don't know why. Maybe you could help me with this, please.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.