-
Notifications
You must be signed in to change notification settings - Fork 47
Synchronizing components
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. And while you can set up your own packets and callbacks to keep your players updated, Cardinal Components API offers you facilities to handle synchronization with little to no effort.
The first step to have your component synchronized, is to implement the AutoSyncedComponent interface.
For example:
// Here, IntComponent is a custom Component specialization
public class SyncedIntComponent implements IntComponent, AutoSyncedComponent {
private int value;
// 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 the provider 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 ComponentKey#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):
private final Object provider;
public IntComponent(Object provider) {
this.provider = provider;
}
public void setValue(int value) {
this.value = value;
MyComponents.MAGIK.sync(this.provider); // assuming MAGIK is the right key for this component
}However, if your component gets updated several times per tick, you should synchronize externally to avoid spamming packets:
FluidComponent component = BikeshedComponents.FLUID.get(provider);
component.setPressure(5);
component.drain(dest, 2);
component.setPressure(0);
BikeshedComponents.FLUID.sync(provider);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, ServerPlayerEntity player, int syncOp) {
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 shouldSyncWith method:
@Override
public void shouldSyncWith(ServerPlayerEntity player, int syncOp) {
return player == this.provider; // only sync with the provider itself
}Remember: any unnecessary information is both a burden on the network, and an opportunity for players to cheat!
When you trigger component synchronization yourself, you may not always want the default full sync behaviour. For example, you may have a lot of data and want to sync only a subset of it, or you may want to play visual effects on the client when they receive the packet. The syncOp parameter gives you this additional flexibility without custom packets.
You can use the syncOp value to carry whatever information you want. The only rule is that a value of 0 triggers the default sync behaviour.
For example, if you want to play particle effects when the counter increases:
public class SyncedIntComponent implements IntComponent, AutoSyncedComponent {
public static final int INCREASE_VALUE = 1;
private int value;
public void increment() {
this.value++;
// Pass a custom value for syncOp
MyComponents.MAGIK.sync(this.provider, INCREASE_VALUE);
}
@Override
public void writeToPacket(PacketByteBuf buf, ServerPlayerEntity player, int syncOp) {
buf.writeVarInt(this.value);
// Write different information to the packet based on syncOp
buf.writeBoolean(syncOp == INCREASE_VALUE);
}
@Override
public void readFromPacket(PacketByteBuf buf) {
this.value = buf.readVarInt();
// Only play particle effects if the synchronization was triggered by increment()
if (buf.readBoolean()) {
MinecraftClient.getInstance().particleManager.addEmitter(entity, ParticleTypes.TOTEM_OF_UNDYING, 20);
}
}
}