Skip to content

A tiny PaperMC library that backports the 1.20+ bundle packet feature to the older versions via selective Nagle's algorithm.

License

Notifications You must be signed in to change notification settings

DrupalDoesNotExists/crossbundlelib

Repository files navigation

CrossBundleLib

Java Kotlin Gradle JitPack

CrossBundleLib (a.k.a. Bundle Lib) is a tiny PaperMC library that backports the 1.20+ bundle packet feature to the older versions. It uses the selective simplified Nagle's algorithm to join needed packets together and send them as a single TCP packet. This guarantees that client receives all the needed packets at once and handles them as close as possible. Library supports multiple Minecraft versions via adapters and Gradle multi-project builds.

Library is expected to work properly on PaperMC! Other platforms such as SpigotMC and PaperMC forks are expected to be supported too.

If you find this library useful and appreciate the work of our contributors, ⭐ star this repository!

Minecraft version BundleLib adapter Tests
1.20.x ✅ bundlelib-v1_20 🔶
1.19.x ✅ bundlelib-v1_19 🔶
1.18.x ✅ bundlelib-v1_18 🔶
1.17.x ✅ bundlelib-v1_17
1.16.x ✅ bundlelib-v1_16
1.15.x 🔨
1.14.x 🔨
1.13.x 🔨
1.12.x 🔨
1.11.x 🔨
1.10.x 🔨
1.9.x 🔨
1.8.x 🔨

🔶 means that the version is not tested yet, but expected to work as no breaking changes were made.

Installation

repositories {
    // ...
    maven("https://jitpack.io/")
}

dependencies {
    implementation("com.github.drupaldoesnotexists.crossbundlelib:bundlelib-core:<VERSION>")
    implementation("com.github.drupaldoesnotexists.crossbundlelib:<ADAPTER>:<VERSION>")
}

Using API

As BundleLib uses a custom version-, adapter- and, therefore, platform-compliant wrapper around bundles, sometimes it requires to inject a custom Netty encoder, so it could delegate to a custom bundle writing mechanism.

Injection can be done only once per channel, so ChannelAlreadyInjectedException is expected to be thrown. Common adapter implementation also checks if previously injected adapter is the same as currently used and if it is, then injection is canceled quietly.

Do note that the Player channel is unreachable during the PlayerLoginEvent, so You have to schedule the task with scheduler or use some later events like PlayerJoinEvent.

⚠️ Because of the mechanism paperweight-based adapters under 1.20 use to identify compatibility with the current platform, you should always specify the 1.20 adapter first in the adapter list! Otherwise, some other non-native adapter will be used. This, no doubt, will work, but using a native implementation is always better.

private final CrossBundleLib lib = new CrossBundleLib(
        new BundleLibAdapter20() /* new BundleLibAdapter17(), ... */
);

@EventHandler
public void onLogin(PlayerLoginEvent event) {
    new BukkitRunnable() {
        @Override
        public void run() {
            lib.inject(event.getPlayer());
        }
    }.runTaskLaterAsynchronously(plugin, 20L);
}

// Or use a PlayerJoinEvent, it really depends on Your problem...

@EventHandler
public void onJoin(PlayerJoinEvent event) {
    lib.inject(event.getPlayer());
}

// And you can freely create a bundle!

Bundle<Packet<?>> myBundle = lib.createBundle(packet1, packet2, packet3);
// fakePlayer.send(myBundle.asPacket()); <-- Send if you need to

What problems does it solve

Originally, this library solves the issue #40 (russian) which describes usage of such a library for implementing a ping-aware audio player with fine-grained server-side control over the music.

There are bugs that could be fixed using bundles, such as entity spawn and entity equipment being two different packets on the old versions, so this creates a flickering on the entities with equipment.

Core concept

This section mainly describes the replication mechanism on the older versions. On the 1.20.x and newer native adapter falls back to the NMS bundles implementation and native packets instead of backporting.

As you all know, Minecraft networking is built on top of Netty. Packets are handled by specific listeners called ChannelHandler's in a specific order defined by a ChannelPipeline. E.g., PacketDecoder is responsible for converting the ByteBuffer to a POJO, PacketEncoder is doing a reverse job.

But PacketDecoder actually can't handle more than a single packet in a buffer at a time. So in every version there is a channel handler called splitter (class names are different among versions, e.g. VarInt21FrameDecoder in 1.17.1).

It grabs a large byte buffer of all the incoming bytes and splits it by reading the length field of every single incoming packet, so only the small single-packet buffers are passed to the packet decoder standing right next. As splitters are present in every version starting (probably even sooner) from 1.7 to the newest versions, "Nagled" packets are perfectly supported and could be understood by the client easily.

So to replicate the bundle behavior, BundleLib joins bundled packets to the single TCP packet (that's a simplified Nagle's algorithm, a.k.a. Nagling). As a client receives all the needed packets at once, the latency between handling them is minimal.

A bit of issues

This section is only applicable for the replicating adapters as it describes the replication mechanism issues.

As you may notice, the client also uses the "tick" principle to handle some of the packets received from the server. The majority of packets are handled instantly, but some of them are "deferred": changes such as velocities are applied only the next tick, etc.

Native bundles actually execute all the incoming packets at the same time, and this makes a guarantee that handlers schedule their actions on the same tick.

But when we replicate things, the client does not have support yet: some tick-bound packets may get scheduled to the two adjacent ticks instead of the same one. So keep that in mind when designing your solutions.

It is also known that some issues with world desync may occur if block updates are sent inaccurately.

Also note that replication is limited within a single TCP packet size bounds. You can stack data up to 64KBs. That should be enough for almost all use cases.

Licensing and contributing

CrossBundleLib is available under the MIT license. Any kind of contributions is welcome. Feel free to open an issue, discussion or PR.

Discussions are available under their own tab.

Look at the list of our amazing contributors!

Special thanks

About

A tiny PaperMC library that backports the 1.20+ bundle packet feature to the older versions via selective Nagle's algorithm.

Resources

License

Stars

Watchers

Forks

Packages

No packages published