Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the networking simpler and universal for everyone #70

Merged
merged 8 commits into from
Feb 11, 2025

Conversation

gungun974
Copy link
Contributor

It's not a secret BTA networking is kind of a mystery for a lot of people.
They're also the fact that BTA got two kind of configuration, the client which we have the rendering and the server which with have the world logic. But this would forget that BTA also support an hybrid between the two called SinglePlayer.
Also the Packet system in BTA is prone too conflict since id are only one byte and the number of id available for everyone is limited.

This PR do two things to help this :

  1. I introduce a tiny helper called the EnvironmenttHelper where we can easily know if we are : Server, Client, SinglePlayer
  2. I add a new way to handle Packet called NetworkMessage

NetworkMessage is an Interface with a role similar as Packet except I don't force you to extend from a class and most important don't make you depend directly on the way BTA since sometime if you don't consume every byte send by BTA, some strange network error can occurred or more often garbage data will be read as invalid packet (trust me, this is weird like bug).

Here an example of a NetworkMessage

public class OpenInventoryNetworkMessage implements NetworkMessage {
	String text;

	public OpenInventoryNetworkMessage() {}

	public OpenInventoryNetworkMessage(String text) {
		this.text = text;
	}


	@Override
	public void encodeToUniversalPacket(@NotNull UniversalPacket packet) {
		packet.writeString(text);
	}

	@Override
	public void decodeFromUniversalPacket(@NotNull UniversalPacket packet) {
		text = packet.readString();

	}

	/**
	 * This should be its own function since LocalPlayer don't exist on server environment
	 */
	@Environment(EnvType.CLIENT)
	private void clientOpenInventory() {
		final Player currentPlayer = Minecraft.getMinecraft().thePlayer;
		currentPlayer.displayContainerScreen(currentPlayer.inventory);
	}

	@Override
	public void handle(NetworkContext context) {
		ExampleMod.LOGGER.info("Receive the message : {}", text);
		if (EnvironmentHelper.isSinglePlayer() || EnvironmentHelper.isClientWorld()) {
			clientOpenInventory();
		}
	}
}

Here you can see our message is separate in three part :

  • Encoding is where we will convert our Message object into an UniversalPacket
  • Decoding is where we will read our UniversalPacket to reconstruct our message
  • Handle will be executed when the received message is ready.

Please note the network system I present you will skip the encoding and decoding if we try to send a packet from SinglePlayer for performance reason.

In my example you can also see something I called the UniversalPacket, this a class that instead from the BTA Packet and is 100% inspired by Java DataInput / DataOutput, netty ByteBuf or modern Minecraft PacketByteBuf

This class has some more advantage than a Java data stream since it will never throw IOException or need try catch.
Also this class got some more Minecraft oriented helper like be capable to write or read NBT data directly in it.

So now we got our NetworkMessage, we need to register in the start of our game, for that it's very easy since we can literally register it our ModInitializer

public class ExampleMod implements ModInitializer {
    public static final String MOD_ID = "examplemod";
    public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
    @Override
    public void onInitialize() {
        LOGGER.info("ExampleMod initialized.");

		NetworkHandler.registerNetworkMessage(OpenInventoryNetworkMessage::new);
    }
}

The NetworkHandler is the master brain of the messaging system, your NetworkMessage and your message is ready to be send and receive from everywhere without any conflict ! It's magic !.

Now you can just use the NetworkHandler from everywhere to do thing like this :

public class TestCommand implements CommandManager.CommandRegistry {

	@Override
	public void register(CommandDispatcher<CommandSource> commandDispatcher) {
		LiteralArgumentBuilder<CommandSource> builder = LiteralArgumentBuilder.<CommandSource>literal("test");

		builder.then(LiteralArgumentBuilder.<CommandSource>literal("openInventory").executes((c) -> {
			return 1;
		}).executes((c) -> {
			if (EnvironmentHelper.isServerEnvironment()){
				NetworkHandler.sendToAllPlayers(new OpenInventoryNetworkMessage("Hoi from server"));
				return 1;
			}
			if (EnvironmentHelper.isSinglePlayer()){
				NetworkHandler.sendToAllPlayers(new OpenInventoryNetworkMessage("Hoi from single player"));
			}
			return 1;
		}));

		commandDispatcher.register(builder);
	}
}

It's that easy, to recap all you need at the end using this abstraction is :

  • Create your NetworkMessage with encoding, decoding and handling
  • Register your NetworkMessage in your mod Initializer.
  • Use NetworkHander from everywhere to send your message and profit !

For the full example in a tiny project you can look at here https://github.com/gungun974/halplibe-network-message-example
In this example I added the command /test openInventory which will send to the client console a message that contain a custom string from the server and will open the inventory for the player.

Please note this is a demonstration of the messaging system, the code for opening the player inventory is a bit broken to be honest.

Of course this PR is a proposal and I would hope to discuss on what we can approve. I think adding to halplibe a standard way to communicate the client server with SinglePlayer compatibility is something important for the future of BTA modding.

Thank for reading all of this ^^

# Conflicts:
#	src/main/java/turniplabs/halplibe/mixin/MinecraftMixin.java
@MartinSVK12 MartinSVK12 merged commit ef1b1f8 into Turnip-Labs:7.3 Feb 11, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants