Skip to content

Synchronizing components

Pyrofab edited this page Aug 12, 2020 · 14 revisions

Storing data is all well and good, but sometimes you need clients to be aware of what you put there. Most often it will be for visual effects, although it can be required for various clientside behaviour like player movement.

Synchronizing a Component

The first step to have your component synchronized, is to implement the SyncedComponent interface. This is usually (but not necessarily) done through one of the specialized subinterfaces provided by the relevant API module - eg. the cardinal-components-entity module provides EntitySyncedComponent. Those specialized interfaces generally require you to implement a single method, a getter for the provider the component is attached to.

For example:

// Here, IntComponent is a custom Component specialization
public class EntityIntComponent implements IntComponent, EntitySyncedComponent {
    private int value;
    private final Entity provider;

    public EntityIntComponent(Entity provider) {
        this.provider = provider;
    }

    @Override
    public Entity getEntity() {
         return this.provider;
    }

    // getters, setters, and serialization methods omitted for brevity
}

With that, all the data that you save through your serialization methods gets automatically synchronized whenever a player starts tracking an entity to which your component is attached. If your component has its value set during initialization and never changes, this would technically be enough. However, most components store dynamic values, which means you need to notify players of your changes. For that, all you need to do is call sync() whenever you update your component.

If your component is updated atomically, through a single method, you can add the call to sync() in that method (this is the most common strategy):

    public void setValue(int value) {
        this.value = value;
        this.sync();
    }

However, if your component gets updated several times per tick, you should synchronize externally to avoid spamming packets:

component.setPressure(5);
component.drain(dest, 2);
component.setPressure(0);
component.sync();

Optimizing network usage

Often, only a subset of the full data needs to be synchronized. To avoid unnecessarily bloated packets, you should override the writeToPacket and readFromPacket methods:

    @Override
    public void writeToPacket(PacketByteBuf buf) {
        buf.writeVarInt(this.value); // only synchronize the information you need!
    }

    @Override
    public void readFromPacket(PacketByteBuf buf) {
        this.value = buf.readVarInt();
    }

Let's say your IntComponent stores how many spells a player can cast. If you broadcast that information to everyone on a PvP server (without an intended way of visualizing it), not only are you sending lots of unnecessary packets, but someone with a cheat mod could get an unfair advantage. This problem can be solved by overriding the syncWith method:

    @Override
    public void syncWith(ServerPlayerEntity player) {
        if (player == this.provider) EntitySyncedComponent.super.syncWith(player);
    }

Remember: any unnecessary information is both a burden on the network, and an opportunity for players to cheat!

Clone this wiki locally