[1.20.2] Support common registration packets. Add configuration task API ()

* Config networking refactor :)

* Add some unit tests for common packets.

* write FabricPacket on network thread.
Split ServerConfigurationConnectionEvents into two.

* Fixes

* Rename event

* Add a testmod + ssome docs

* Improve registry sync fixing deadlock in a number of cases.

* Cleanup channel events.

* Review feedback and fixes.
This commit is contained in:
modmuss 2023-08-09 16:30:51 +01:00 committed by GitHub
parent 86b12645b9
commit 0b2eb405dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 1642 additions and 303 deletions
build.gradle
fabric-networking-api-v1/src
fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient
fabric-registry-sync-v0/src
client/java/net/fabricmc/fabric/impl/client/registry/sync
main/java/net/fabricmc/fabric/impl/registry/sync

View file

@ -223,6 +223,7 @@ allprojects {
testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}"
testImplementation sourceSets.testmodClient.output
testImplementation 'org.mockito:mockito-core:5.4.0'
}
test {

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.client.networking.v1;
import java.util.List;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientConfigurationNetworkHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
/**
* Offers access to events related to the indication of a connected server's ability to receive packets in certain channels.
*/
public final class C2SConfigurationChannelEvents {
/**
* An event for the client configuration network handler receiving an update indicating the connected server's ability to receive packets in certain channels.
* This event may be invoked at any time after login and up to disconnection.
*/
public static final Event<Register> REGISTER = EventFactory.createArrayBacked(Register.class, callbacks -> (handler, sender, client, channels) -> {
for (Register callback : callbacks) {
callback.onChannelRegister(handler, sender, client, channels);
}
});
/**
* An event for the client configuration network handler receiving an update indicating the connected server's lack of ability to receive packets in certain channels.
* This event may be invoked at any time after login and up to disconnection.
*/
public static final Event<Unregister> UNREGISTER = EventFactory.createArrayBacked(Unregister.class, callbacks -> (handler, sender, client, channels) -> {
for (Unregister callback : callbacks) {
callback.onChannelUnregister(handler, sender, client, channels);
}
});
private C2SConfigurationChannelEvents() {
}
/**
* @see C2SConfigurationChannelEvents#REGISTER
*/
@FunctionalInterface
public interface Register {
void onChannelRegister(ClientConfigurationNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
}
/**
* @see C2SConfigurationChannelEvents#UNREGISTER
*/
@FunctionalInterface
public interface Unregister {
void onChannelUnregister(ClientConfigurationNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
}
}

View file

@ -18,10 +18,7 @@ package net.fabricmc.fabric.api.client.networking.v1;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientConfigurationNetworkHandler;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.util.Identifier;
@ -53,28 +50,6 @@ public final class C2SPlayChannelEvents {
}
});
/**
* An event for the client configuration network handler receiving an update indicating the connected server's ability to receive packets in certain channels.
* This event may be invoked at any time after login and up to disconnection.
*/
@ApiStatus.Experimental
public static final Event<RegisterConfiguration> REGISTER_CONFIGURATION = EventFactory.createArrayBacked(RegisterConfiguration.class, callbacks -> (handler, sender, client, channels) -> {
for (RegisterConfiguration callback : callbacks) {
callback.onChannelRegister(handler, sender, client, channels);
}
});
/**
* An event for the client configuration network handler receiving an update indicating the connected server's lack of ability to receive packets in certain channels.
* This event may be invoked at any time after login and up to disconnection.
*/
@ApiStatus.Experimental
public static final Event<UnregisterConfiguration> UNREGISTER_CONFIGURATION = EventFactory.createArrayBacked(UnregisterConfiguration.class, callbacks -> (handler, sender, client, channels) -> {
for (UnregisterConfiguration callback : callbacks) {
callback.onChannelUnregister(handler, sender, client, channels);
}
});
private C2SPlayChannelEvents() {
}
@ -93,22 +68,4 @@ public final class C2SPlayChannelEvents {
public interface Unregister {
void onChannelUnregister(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
}
/**
* @see C2SPlayChannelEvents#REGISTER_CONFIGURATION
*/
@FunctionalInterface
@ApiStatus.Experimental
public interface RegisterConfiguration {
void onChannelRegister(ClientConfigurationNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
}
/**
* @see C2SPlayChannelEvents#UNREGISTER_CONFIGURATION
*/
@FunctionalInterface
@ApiStatus.Experimental
public interface UnregisterConfiguration {
void onChannelUnregister(ClientConfigurationNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
}
}

View file

@ -31,7 +31,6 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.thread.ThreadExecutor;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.PacketType;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
@ -384,9 +383,14 @@ public final class ClientConfigurationNetworking {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
PacketByteBuf buf = PacketByteBufs.create();
packet.write(buf);
send(packet.getType().getId(), buf);
final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
if (addon != null) {
addon.sendPacket(packet);
return;
}
throw new IllegalStateException("Cannot send packet while not configuring!");
}
private ClientConfigurationNetworking() {

View file

@ -31,7 +31,6 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.thread.ThreadExecutor;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.PacketType;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
@ -339,6 +338,16 @@ public final class ClientPlayNetworking {
return ClientNetworkingImpl.createC2SPacket(channelName, buf);
}
/**
* Creates a packet which may be sent to the connected server.
*
* @param packet the fabric packet
* @return a new packet
*/
public static <T extends FabricPacket> Packet<ServerCommonPacketListener> createC2SPacket(T packet) {
return ClientNetworkingImpl.createC2SPacket(packet);
}
/**
* Gets the packet sender which sends packets to the connected server.
*
@ -381,9 +390,13 @@ public final class ClientPlayNetworking {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
PacketByteBuf buf = PacketByteBufs.create();
packet.write(buf);
send(packet.getType().getId(), buf);
// You cant send without a client player, so this is fine
if (MinecraftClient.getInstance().getNetworkHandler() != null) {
MinecraftClient.getInstance().getNetworkHandler().sendPacket(createC2SPacket(packet));
return;
}
throw new IllegalStateException("Cannot send packets when not in game!");
}
private ClientPlayNetworking() {

View file

@ -27,16 +27,18 @@ import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents;
import net.fabricmc.fabric.api.client.networking.v1.C2SConfigurationChannelEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload;
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientCommonNetworkHandlerAccessor;
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientConfigurationNetworkHandlerAccessor;
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientLoginNetworkHandlerAccessor;
public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetworkAddon<ClientConfigurationNetworking.ConfigurationChannelHandler> {
private final ClientConfigurationNetworkHandler handler;
@ -65,8 +67,17 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
}
public void onServerReady() {
this.sendInitialChannelRegistrationPacket();
this.sentInitialRegisterPacket = true;
// Do nothing for now
}
@Override
protected void receiveRegistration(boolean register, PacketByteBuf buf) {
super.receiveRegistration(register, buf);
if (register && !this.sentInitialRegisterPacket) {
this.sendInitialChannelRegistrationPacket();
this.sentInitialRegisterPacket = true;
}
}
/**
@ -100,14 +111,19 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
return ClientPlayNetworking.createC2SPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
return ClientPlayNetworking.createC2SPacket(packet);
}
@Override
protected void invokeRegisterEvent(List<Identifier> ids) {
C2SPlayChannelEvents.REGISTER_CONFIGURATION.invoker().onChannelRegister(this.handler, this, this.client, ids);
C2SConfigurationChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.client, ids);
}
@Override
protected void invokeUnregisterEvent(List<Identifier> ids) {
C2SPlayChannelEvents.UNREGISTER_CONFIGURATION.invoker().onChannelUnregister(this.handler, this, this.client, ids);
C2SConfigurationChannelEvents.UNREGISTER.invoker().onChannelUnregister(this.handler, this, this.client, ids);
}
@Override
@ -147,6 +163,10 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
@Override
protected boolean isReservedChannel(Identifier channelName) {
return NetworkingImpl.isReservedPlayChannel(channelName);
return NetworkingImpl.isReservedCommonChannel(channelName);
}
public ChannelInfoHolder getChannelInfoHolder() {
return (ChannelInfoHolder) ((ClientLoginNetworkHandlerAccessor) handler).getConnection();
}
}

View file

@ -16,15 +16,13 @@
package net.fabricmc.fabric.impl.networking.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ConnectScreen;
import net.minecraft.client.network.ClientConfigurationNetworkHandler;
import net.minecraft.client.network.ClientLoginNetworkHandler;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.ClientConnection;
@ -40,20 +38,24 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworkin
import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.impl.networking.CommonPacketsImpl;
import net.fabricmc.fabric.impl.networking.CommonRegisterPayload;
import net.fabricmc.fabric.impl.networking.CommonVersionPayload;
import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.FabricPacketPayload;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload;
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientLoginNetworkHandlerAccessor;
import net.fabricmc.fabric.mixin.networking.client.accessor.ConnectScreenAccessor;
import net.fabricmc.fabric.mixin.networking.client.accessor.MinecraftClientAccessor;
public final class ClientNetworkingImpl {
public static final GlobalReceiverRegistry<ClientLoginNetworking.LoginQueryRequestHandler> LOGIN = new GlobalReceiverRegistry<>();
public static final GlobalReceiverRegistry<ClientConfigurationNetworking.ConfigurationChannelHandler> CONFIGURATION = new GlobalReceiverRegistry<>();
public static final GlobalReceiverRegistry<ClientPlayNetworking.PlayChannelHandler> PLAY = new GlobalReceiverRegistry<>();
public static final GlobalReceiverRegistry<ClientLoginNetworking.LoginQueryRequestHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkState.LOGIN);
public static final GlobalReceiverRegistry<ClientConfigurationNetworking.ConfigurationChannelHandler> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkState.CONFIGURATION);
public static final GlobalReceiverRegistry<ClientPlayNetworking.PlayChannelHandler> PLAY = new GlobalReceiverRegistry<>(NetworkState.PLAY);
private static ClientPlayNetworkAddon currentPlayAddon;
private static ClientConfigurationNetworkAddon currentConfigurationAddon;
@ -61,6 +63,10 @@ public final class ClientNetworkingImpl {
return (ClientPlayNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
}
public static ClientConfigurationNetworkAddon getAddon(ClientConfigurationNetworkHandler handler) {
return (ClientConfigurationNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
}
public static ClientLoginNetworkAddon getAddon(ClientLoginNetworkHandler handler) {
return (ClientLoginNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
}
@ -69,6 +75,19 @@ public final class ClientNetworkingImpl {
return new CustomPayloadC2SPacket(new PacketByteBufPayload(channelName, buf));
}
public static Packet<ServerCommonPacketListener> createC2SPacket(FabricPacket packet) {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
if (NetworkingImpl.WRITE_FABRIC_PACKET_CALLING_THREAD) {
PacketByteBuf buf = PacketByteBufs.create();
packet.write(buf);
return createC2SPacket(packet.getType().getId(), buf);
}
return new CustomPayloadC2SPacket(new FabricPacketPayload(packet));
}
/**
* Due to the way logging into a integrated or remote dedicated server will differ, we need to obtain the login client connection differently.
*/
@ -134,29 +153,44 @@ public final class ClientNetworkingImpl {
currentConfigurationAddon = null;
});
// Register a login query handler for early channel registration.
ClientLoginNetworking.registerGlobalReceiver(NetworkingImpl.EARLY_REGISTRATION_CHANNEL, (client, handler, buf, listenerAdder) -> {
int n = buf.readVarInt();
List<Identifier> ids = new ArrayList<>(n);
// Version packet
ClientConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.PACKET_ID, (client, handler, buf, responseSender) -> {
var payload = new CommonVersionPayload(buf);
int negotiatedVersion = handleVersionPacket(payload, responseSender);
ClientNetworkingImpl.getAddon(handler).onCommonVersionPacket(negotiatedVersion);
});
for (int i = 0; i < n; i++) {
ids.add(buf.readIdentifier());
// Register packet
ClientConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.PACKET_ID, (client, handler, buf, responseSender) -> {
var payload = new CommonRegisterPayload(buf);
ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getAddon(handler);
if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) {
if (payload.version() != addon.getNegotiatedVersion()) {
throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(addon.getNegotiatedVersion(), payload.version()));
}
addon.getChannelInfoHolder().getPendingChannelsNames(NetworkState.PLAY).addAll(payload.channels());
NetworkingImpl.LOGGER.debug("Received accepted channels from the server");
responseSender.sendPacket(new CommonRegisterPayload(addon.getNegotiatedVersion(), CommonRegisterPayload.PLAY_PHASE, ClientPlayNetworking.getGlobalReceivers()));
} else {
addon.onCommonRegisterPacket(payload);
responseSender.sendPacket(addon.createRegisterPayload());
}
ClientConnection connection = ((ClientLoginNetworkHandlerAccessor) handler).getConnection();
((ChannelInfoHolder) connection).getPendingChannelsNames(NetworkState.PLAY).addAll(ids);
NetworkingImpl.LOGGER.debug("Received accepted channels from the server");
PacketByteBuf response = PacketByteBufs.create();
Collection<Identifier> channels = ClientPlayNetworking.getGlobalReceivers();
response.writeVarInt(channels.size());
for (Identifier id : channels) {
response.writeIdentifier(id);
}
NetworkingImpl.LOGGER.debug("Sent accepted channels to the server");
return CompletableFuture.completedFuture(response);
});
}
// Disconnect if there are no commonly supported versions.
// Client responds with the intersection of supported versions.
// Return the highest supported version
private static int handleVersionPacket(CommonVersionPayload payload, PacketSender packetSender) {
int version = CommonPacketsImpl.getHighestCommonVersion(payload.versions(), CommonPacketsImpl.SUPPORTED_COMMON_PACKET_VERSIONS);
if (version <= 0) {
throw new UnsupportedOperationException("Client does not support any requested versions from server");
}
packetSender.sendPacket(new CommonVersionPayload(new int[]{ version }));
return version;
}
}

View file

@ -33,6 +33,7 @@ import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
@ -109,6 +110,11 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
return ClientPlayNetworking.createC2SPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
return ClientPlayNetworking.createC2SPacket(packet);
}
@Override
protected void invokeRegisterEvent(List<Identifier> ids) {
C2SPlayChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.client, ids);
@ -151,6 +157,6 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
@Override
protected boolean isReservedChannel(Identifier channelName) {
return NetworkingImpl.isReservedPlayChannel(channelName);
return NetworkingImpl.isReservedCommonChannel(channelName);
}
}

View file

@ -57,7 +57,7 @@ import net.minecraft.server.network.ServerPlayerEntity;
*
* @see ServerPlayNetworking#registerGlobalReceiver(PacketType, ServerPlayNetworking.PlayPacketHandler)
* @see ServerPlayNetworking#send(ServerPlayerEntity, PacketType, FabricPacket)
* @see PacketSender#sendPacket(PacketType, FabricPacket)
* @see PacketSender#sendPacket(FabricPacket)
*/
public interface FabricPacket {
/**

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.networking.v1;
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.server.network.ServerPlayerConfigurationTask;
import net.minecraft.util.Identifier;
/**
* Fabric-provided extensions for {@link ServerConfigurationNetworkHandler}.
* This interface is automatically implemented via Mixin and interface injection.
*/
public interface FabricServerConfigurationNetworkHandler {
/**
* Enqueue a {@link ServerPlayerConfigurationTask} task to be processed.
*
* <p>Before adding a task use {@link ServerConfigurationNetworking#canSend(ServerConfigurationNetworkHandler, Identifier)}
* to ensure that the client can process this task.
*
* <p>Once the client has handled the task a packet should be sent to the server.
* Upon receiving this packet the server should call {@link FabricServerConfigurationNetworkHandler#completeTask(ServerPlayerConfigurationTask.Key)}
*
* @param task the task
*/
default void addTask(ServerPlayerConfigurationTask task) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
*
* @param key the key
*/
default void completeTask(ServerPlayerConfigurationTask.Key key) {
throw new UnsupportedOperationException("Implemented via mixin");
}
}

View file

@ -23,9 +23,10 @@ import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder;
@ -43,6 +44,13 @@ public interface PacketSender {
*/
Packet<?> createPacket(Identifier channelName, PacketByteBuf buf);
/**
* Makes a packet for a fabric packet.
*
* @param packet the fabric packet
*/
Packet<?> createPacket(FabricPacket packet);
/**
* Sends a packet.
*
@ -57,9 +65,17 @@ public interface PacketSender {
* @param packet the packet
*/
default <T extends FabricPacket> void sendPacket(T packet) {
sendPacket(createPacket(packet));
}
/**
* Sends a packet.
* @param payload the payload
*/
default void sendPacket(CustomPayload payload) {
PacketByteBuf buf = PacketByteBufs.create();
packet.write(buf);
sendPacket(packet.getType().getId(), buf);
payload.write(buf);
sendPacket(payload.id(), buf);
}
/**
@ -77,9 +93,19 @@ public interface PacketSender {
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
*/
default <T extends FabricPacket> void sendPacket(T packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
sendPacket(createPacket(packet), callback);
}
/**
* Sends a packet.
*
* @param payload the payload
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
*/
default void sendPacket(CustomPayload payload, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
PacketByteBuf buf = PacketByteBufs.create();
packet.write(buf);
sendPacket(packet.getType().getId(), buf, callback);
payload.write(buf);
sendPacket(payload.id(), buf, callback);
}
/**
@ -97,9 +123,19 @@ public interface PacketSender {
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
*/
default <T extends FabricPacket> void sendPacket(T packet, @Nullable PacketCallbacks callback) {
sendPacket(createPacket(packet), callback);
}
/**
* Sends a packet.
*
* @param payload the payload
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
*/
default void sendPacket(CustomPayload payload, @Nullable PacketCallbacks callback) {
PacketByteBuf buf = PacketByteBufs.create();
packet.write(buf);
sendPacket(packet.getType().getId(), buf, callback);
payload.write(buf);
sendPacket(payload.id(), buf, callback);
}
/**

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.networking.v1;
import java.util.List;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Offers access to events related to the indication of a connected client's ability to receive packets in certain channels.
*/
public final class S2CConfigurationChannelEvents {
/**
* An event for the server configuration network handler receiving an update indicating the connected client's ability to receive packets in certain channels.
* This event may be invoked at any time after login and up to disconnection.
*/
public static final Event<Register> REGISTER = EventFactory.createArrayBacked(Register.class, callbacks -> (handler, sender, server, channels) -> {
for (Register callback : callbacks) {
callback.onChannelRegister(handler, sender, server, channels);
}
});
/**
* An event for the server configuration network handler receiving an update indicating the connected client's lack of ability to receive packets in certain channels.
* This event may be invoked at any time after login and up to disconnection.
*/
public static final Event<Unregister> UNREGISTER = EventFactory.createArrayBacked(Unregister.class, callbacks -> (handler, sender, server, channels) -> {
for (Unregister callback : callbacks) {
callback.onChannelUnregister(handler, sender, server, channels);
}
});
private S2CConfigurationChannelEvents() {
}
/**
* @see S2CConfigurationChannelEvents#REGISTER
*/
@FunctionalInterface
public interface Register {
void onChannelRegister(ServerConfigurationNetworkHandler handler, PacketSender sender, MinecraftServer server, List<Identifier> channels);
}
/**
* @see S2CConfigurationChannelEvents#UNREGISTER
*/
@FunctionalInterface
public interface Unregister {
void onChannelUnregister(ServerConfigurationNetworkHandler handler, PacketSender sender, MinecraftServer server, List<Identifier> channels);
}
}

View file

@ -30,10 +30,37 @@ import net.fabricmc.fabric.api.event.EventFactory;
@ApiStatus.Experimental
public class ServerConfigurationConnectionEvents {
/**
* Event indicating a connection began sending configuration packets.
* Event fired before any vanilla configuration has taken place.
*
* <p>This event is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}
*
* <p>Task queued during this event will complete before vanilla configuration starts.
*/
public static final Event<Send> SEND = EventFactory.createArrayBacked(Send.class, callbacks -> (handler, server) -> {
for (Send callback : callbacks) {
public static final Event<Configure> BEFORE_CONFIGURE = EventFactory.createArrayBacked(Configure.class, callbacks -> (handler, server) -> {
for (Configure callback : callbacks) {
callback.onSendConfiguration(handler, server);
}
});
/**
* Event fired during vanilla configuration.
*
* <p>This event is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}
*
* <p>An example usage of this:
* <pre>{@code
* ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
* if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.PACKET_TYPE)) {
* handler.addTask(new TestConfigurationTask("Example data"));
* } else {
* // You can opt to disconnect the client if it cannot handle the configuration task
* handler.disconnect(Text.literal("Network test configuration not supported by client"));
* }
* });
* }</pre>
*/
public static final Event<Configure> CONFIGURE = EventFactory.createArrayBacked(Configure.class, callbacks -> (handler, server) -> {
for (Configure callback : callbacks) {
callback.onSendConfiguration(handler, server);
}
});
@ -50,7 +77,7 @@ public class ServerConfigurationConnectionEvents {
});
@FunctionalInterface
public interface Send {
public interface Configure {
void onSendConfiguration(ServerConfigurationNetworkHandler handler, MinecraftServer server);
}

View file

@ -96,18 +96,7 @@ public final class ServerConfigurationNetworking {
@Override
public void receive(MinecraftServer server, ServerConfigurationNetworkHandler networkHandler, PacketByteBuf buf, PacketSender sender) {
T packet = type.read(buf);
if (server.isOnThread()) {
// Do not submit to the server thread if we're already running there.
// Normally, packets are handled on the network IO thread - though it is
// not guaranteed (for example, with 1.19.4 S2C packet bundling)
// Since we're handling it right now, connection check is redundant.
handler.receive(packet, sender);
} else {
server.execute(() -> {
if (networkHandler.isConnectionOpen()) handler.receive(packet, sender);
});
}
handler.receive(packet, networkHandler, sender);
}
});
}
@ -160,7 +149,7 @@ public final class ServerConfigurationNetworking {
/**
* Registers a handler to a channel.
* This method differs from {@link ServerConfigurationNetworking#registerGlobalReceiver(Identifier, ConfigurationChannelHandler)} since
* the channel handler will only be applied to the player represented by the {@link ServerConfigurationNetworkHandler}.
* the channel handler will only be applied to the client represented by the {@link ServerConfigurationNetworkHandler}.
*
* <p>The handler runs on the network thread. After reading the buffer there, the world
* must be modified in the server thread by calling {@link ThreadExecutor#execute(Runnable)}.
@ -189,7 +178,7 @@ public final class ServerConfigurationNetworking {
/**
* Registers a handler for a packet type.
* This method differs from {@link ServerConfigurationNetworking#registerGlobalReceiver(PacketType, ConfigurationPacketHandler)} since
* the channel handler will only be applied to the player represented by the {@link ServerConfigurationNetworkHandler}.
* the channel handler will only be applied to the client represented by the {@link ServerConfigurationNetworkHandler}.
*
* <p>For example, if you only register a receiver using this method when a {@linkplain ServerLoginNetworking#registerGlobalReceiver(Identifier, ServerLoginNetworking.LoginQueryResponseHandler)}
* login response has been received, you should use {@link ServerPlayConnectionEvents#INIT} to register the channel handler.
@ -213,18 +202,7 @@ public final class ServerConfigurationNetworking {
@Override
public void receive(MinecraftServer server, ServerConfigurationNetworkHandler networkHandler2, PacketByteBuf buf, PacketSender sender) {
T packet = type.read(buf);
if (server.isOnThread()) {
// Do not submit to the server thread if we're already running there.
// Normally, packets are handled on the network IO thread - though it is
// not guaranteed (for example, with 1.19.4 S2C packet bundling)
// Since we're handling it right now, connection check is redundant.
handler.receive(packet, sender);
} else {
server.execute(() -> {
if (networkHandler2.isConnectionOpen()) handler.receive(packet, sender);
});
}
handler.receive(packet, networkHandler2, sender);
}
});
}
@ -326,6 +304,19 @@ public final class ServerConfigurationNetworking {
return ServerNetworkingImpl.createC2SPacket(channelName, buf);
}
/**
* Creates a packet which may be sent to a connected client.
*
* @param packet the fabric packet
* @return a new packet
*/
public static <T extends FabricPacket> Packet<ClientCommonPacketListener> createS2CPacket(T packet) {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
return ServerNetworkingImpl.createC2SPacket(packet);
}
/**
* Gets the packet sender which sends packets to the connected client.
*
@ -364,9 +355,7 @@ public final class ServerConfigurationNetworking {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
PacketByteBuf buf = PacketByteBufs.create();
packet.write(buf);
handler.sendPacket(createS2CPacket(packet.getType().getId(), buf));
handler.sendPacket(createS2CPacket(packet));
}
// Helper methods
@ -391,7 +380,7 @@ public final class ServerConfigurationNetworking {
* Handles an incoming packet.
*
* <p>This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}.
* Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the provided Minecraft server instance.
* Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the server instance from {@link ServerConfigurationNetworking#getServer(ServerConfigurationNetworkHandler)}.
*
* <p>An example usage of this is:
* <pre>{@code
@ -405,7 +394,7 @@ public final class ServerConfigurationNetworking {
* });
* }</pre>
* @param server the server
* @param handler the network handler that received this packet, representing the player/client who sent the packet
* @param handler the network handler that received this packet, representing the client who sent the packet
* @param buf the payload of the packet
* @param responseSender the packet sender
*/
@ -427,7 +416,10 @@ public final class ServerConfigurationNetworking {
@FunctionalInterface
public interface ConfigurationPacketHandler<T extends FabricPacket> {
/**
* Handles the incoming packet. This is called on the server thread.
* Handles an incoming packet.
*
* <p>Unlike {@link ServerPlayNetworking.PlayPacketHandler} this method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}.
* Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the Minecraft server instance from {@link ServerConfigurationNetworking#getServer(ServerConfigurationNetworkHandler)}.
*
* <p>An example usage of this:
* <pre>{@code
@ -439,9 +431,10 @@ public final class ServerConfigurationNetworking {
*
*
* @param packet the packet
* @param networkHandler the network handler
* @param responseSender the packet sender
* @see FabricPacket
*/
void receive(T packet, PacketSender responseSender);
void receive(T packet, ServerConfigurationNetworkHandler networkHandler, PacketSender responseSender);
}
}

View file

@ -389,6 +389,16 @@ public final class ServerPlayNetworking {
return ServerNetworkingImpl.createC2SPacket(channelName, buf);
}
/**
* Creates a packet which may be sent to a connected client.
*
* @param packet the fabric packet
* @return a new packet
*/
public static <T extends FabricPacket> Packet<ClientCommonPacketListener> createS2CPacket(T packet) {
return ServerNetworkingImpl.createC2SPacket(packet);
}
/**
* Gets the packet sender which sends packets to the connected client.
*
@ -439,9 +449,7 @@ public final class ServerPlayNetworking {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
PacketByteBuf buf = PacketByteBufs.create();
packet.write(buf);
player.networkHandler.sendPacket(createS2CPacket(packet.getType().getId(), buf));
player.networkHandler.sendPacket(createS2CPacket(packet));
}
// Helper methods

View file

@ -46,22 +46,18 @@ import net.fabricmc.fabric.api.networking.v1.PacketSender;
*
* @param <H> the channel handler type
*/
public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAddon<H> implements PacketSender {
public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAddon<H> implements PacketSender, CommonPacketHandler {
protected final ClientConnection connection;
protected final GlobalReceiverRegistry<H> receiver;
protected final Set<Identifier> sendableChannels;
protected final Set<Identifier> sendableChannelsView;
protected int commonVersion = -1;
protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry<H> receiver, ClientConnection connection, String description) {
this(receiver, connection, new HashSet<>(), description);
}
protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry<H> receiver, ClientConnection connection, Set<Identifier> sendableChannels, String description) {
super(receiver, description);
this.connection = connection;
this.receiver = receiver;
this.sendableChannels = sendableChannels;
this.sendableChannelsView = Collections.unmodifiableSet(sendableChannels);
this.sendableChannels = Collections.synchronizedSet(new HashSet<>());
}
public abstract void lateInit();
@ -157,17 +153,22 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
}
this.addId(ids, active);
this.schedule(register ? () -> register(ids) : () -> unregister(ids));
if (register) {
register(ids);
} else {
unregister(ids);
}
}
void register(List<Identifier> ids) {
this.sendableChannels.addAll(ids);
this.invokeRegisterEvent(ids);
schedule(() -> this.invokeRegisterEvent(ids));
}
void unregister(List<Identifier> ids) {
this.sendableChannels.removeAll(ids);
this.invokeUnregisterEvent(ids);
schedule(() -> this.invokeUnregisterEvent(ids));
}
@Override
@ -202,6 +203,62 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
}
public Set<Identifier> getSendableChannels() {
return this.sendableChannelsView;
return Collections.unmodifiableSet(this.sendableChannels);
}
// Common packet handlers
@Override
public void onCommonVersionPacket(int negotiatedVersion) {
assert negotiatedVersion == 1; // We only support version 1 for now
commonVersion = negotiatedVersion;
this.logger.info("Negotiated common packet version {}", commonVersion);
}
@Override
public void onCommonRegisterPacket(CommonRegisterPayload payload) {
if (payload.version() != getNegotiatedVersion()) {
throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(commonVersion, payload.version()));
}
final String currentPhase = getPhase();
if (currentPhase == null) {
// We don't support receiving the register packet during this phase. See getPhase() for supported phases.
// The normal case where the play channels are sent during configuration is handled in the client/common configuration packet handlers.
logger.warn("Received common register packet for phase {} in network state: {}", payload.phase(), receiver.getState());
return;
}
if (!payload.phase().equals(currentPhase)) {
// We need to handle receiving the play phase during configuration!
throw new IllegalStateException("Register packet received for phase (%s) on handler for phase(%s)".formatted(payload.phase(), currentPhase));
}
register(new ArrayList<>(payload.channels()));
}
@Override
public CommonRegisterPayload createRegisterPayload() {
return new CommonRegisterPayload(getNegotiatedVersion(), getPhase(), this.getReceivableChannels());
}
@Override
public int getNegotiatedVersion() {
if (commonVersion == -1) {
throw new IllegalStateException("Not yet negotiated common packet version");
}
return commonVersion;
}
@Nullable
private String getPhase() {
return switch (receiver.getState()) {
case PLAY -> CommonRegisterPayload.PLAY_PHASE;
case CONFIGURATION -> CommonRegisterPayload.CONFIGURATION_PHASE;
default -> null; // We don't support receiving this packet on any other phase
};
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking;
public interface CommonPacketHandler {
void onCommonVersionPacket(int negotiatedVersion);
void onCommonRegisterPacket(CommonRegisterPayload payload);
CommonRegisterPayload createRegisterPayload();
int getNegotiatedVersion();
}

View file

@ -0,0 +1,141 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking;
import java.util.Arrays;
import java.util.function.Consumer;
import net.minecraft.network.NetworkState;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.network.ServerPlayerConfigurationTask;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl;
public class CommonPacketsImpl {
public static final int PACKET_VERSION_1 = 1;
public static final int[] SUPPORTED_COMMON_PACKET_VERSIONS = new int[]{ PACKET_VERSION_1 };
public static void init() {
ServerConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.PACKET_ID, (server, handler, buf, responseSender) -> {
var payload = new CommonVersionPayload(buf);
ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
addon.onCommonVersionPacket(getNegotiatedVersion(payload));
handler.completeTask(CommonVersionConfigurationTask.KEY);
});
ServerConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.PACKET_ID, (server, handler, buf, responseSender) -> {
var payload = new CommonRegisterPayload(buf);
ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) {
if (payload.version() != addon.getNegotiatedVersion()) {
throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(addon.getNegotiatedVersion(), payload.version()));
}
// Play phase hasnt started yet, add them to the pending names.
addon.getChannelInfoHolder().getPendingChannelsNames(NetworkState.PLAY).addAll(payload.channels());
NetworkingImpl.LOGGER.debug("Received accepted channels from the client for play phase");
} else {
addon.onCommonRegisterPacket(payload);
}
handler.completeTask(CommonRegisterConfigurationTask.KEY);
});
// Create a configuration task to send and receive the common packets
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
final ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
if (ServerConfigurationNetworking.canSend(handler, CommonVersionPayload.PACKET_ID)) {
// Tasks are processed in order.
handler.addTask(new CommonVersionConfigurationTask(addon));
if (ServerConfigurationNetworking.canSend(handler, CommonRegisterPayload.PACKET_ID)) {
handler.addTask(new CommonRegisterConfigurationTask(addon));
}
}
});
}
// A configuration phase task to send and receive the version packets.
private record CommonVersionConfigurationTask(ServerConfigurationNetworkAddon addon) implements ServerPlayerConfigurationTask {
public static final Key KEY = new Key(CommonVersionPayload.PACKET_ID.toString());
@Override
public void sendPacket(Consumer<Packet<?>> sender) {
addon.sendPacket(new CommonVersionPayload(SUPPORTED_COMMON_PACKET_VERSIONS));
}
@Override
public Key getKey() {
return KEY;
}
}
// A configuration phase task to send and receive the registration packets.
private record CommonRegisterConfigurationTask(ServerConfigurationNetworkAddon addon) implements ServerPlayerConfigurationTask {
public static final Key KEY = new Key(CommonRegisterPayload.PACKET_ID.toString());
@Override
public void sendPacket(Consumer<Packet<?>> sender) {
addon.sendPacket(addon.createRegisterPayload());
}
@Override
public Key getKey() {
return KEY;
}
}
private static int getNegotiatedVersion(CommonVersionPayload payload) {
int version = getHighestCommonVersion(payload.versions(), SUPPORTED_COMMON_PACKET_VERSIONS);
if (version <= 0) {
throw new UnsupportedOperationException("server does not support any requested versions from client");
}
return version;
}
public static int getHighestCommonVersion(int[] a, int[] b) {
int[] as = a.clone();
int[] bs = b.clone();
Arrays.sort(as);
Arrays.sort(bs);
int ap = as.length - 1;
int bp = bs.length - 1;
while (ap >= 0 && bp >= 0) {
if (as[ap] == bs[bp]) {
return as[ap];
}
if (as[ap] > bs[bp]) {
ap--;
} else {
bp--;
}
}
return -1;
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
public record CommonRegisterPayload(int version, String phase, Set<Identifier> channels) implements CustomPayload {
public static final Identifier PACKET_ID = new Identifier("c", "register");
public static final String PLAY_PHASE = "play";
public static final String CONFIGURATION_PHASE = "configuration";
public CommonRegisterPayload(PacketByteBuf buf) {
this(
buf.readVarInt(),
buf.readString(),
buf.readCollection(HashSet::new, PacketByteBuf::readIdentifier)
);
}
@Override
public void write(PacketByteBuf buf) {
buf.writeVarInt(version);
buf.writeString(phase);
buf.writeCollection(channels, PacketByteBuf::writeIdentifier);
}
@Override
public Identifier id() {
return PACKET_ID;
}
}

View file

@ -14,20 +14,26 @@
* limitations under the License.
*/
package net.fabricmc.fabric.impl.registry.sync;
import java.util.function.Consumer;
import com.mojang.authlib.GameProfile;
package net.fabricmc.fabric.impl.networking;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
public record CommonVersionPayload(int[] versions) implements CustomPayload {
public static final Identifier PACKET_ID = new Identifier("c", "version");
public record ConfiguringServerPlayer(GameProfile gameProfile, Consumer<Packet<?>> sender) {
public void sendPacket(Identifier identifier, PacketByteBuf buf) {
sender.accept(ServerConfigurationNetworking.createS2CPacket(identifier, buf));
public CommonVersionPayload(PacketByteBuf buf) {
this(buf.readIntArray());
}
@Override
public void write(PacketByteBuf buf) {
buf.writeIntArray(versions);
}
@Override
public Identifier id() {
return PACKET_ID;
}
}

View file

@ -27,18 +27,22 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.NetworkState;
import net.minecraft.util.Identifier;
public final class GlobalReceiverRegistry<H> {
private final NetworkState state;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Map<Identifier, H> handlers;
private final Set<AbstractNetworkAddon<H>> trackedAddons = new HashSet<>();
public GlobalReceiverRegistry() {
this(new HashMap<>()); // sync map should be fine as there is little read write competitions
public GlobalReceiverRegistry(NetworkState state) {
this(state, new HashMap<>()); // sync map should be fine as there is little read write competitions
}
public GlobalReceiverRegistry(Map<Identifier, H> map) {
public GlobalReceiverRegistry(NetworkState state, Map<Identifier, H> map) {
this.state = state;
this.handlers = map;
}
@ -58,7 +62,7 @@ public final class GlobalReceiverRegistry<H> {
Objects.requireNonNull(channelName, "Channel name cannot be null");
Objects.requireNonNull(handler, "Channel handler cannot be null");
if (NetworkingImpl.isReservedPlayChannel(channelName)) {
if (NetworkingImpl.isReservedCommonChannel(channelName)) {
throw new IllegalArgumentException(String.format("Cannot register handler for reserved channel with name \"%s\"", channelName));
}
@ -81,7 +85,7 @@ public final class GlobalReceiverRegistry<H> {
public H unregisterGlobalReceiver(Identifier channelName) {
Objects.requireNonNull(channelName, "Channel name cannot be null");
if (NetworkingImpl.isReservedPlayChannel(channelName)) {
if (NetworkingImpl.isReservedCommonChannel(channelName)) {
throw new IllegalArgumentException(String.format("Cannot unregister packet handler for reserved channel with name \"%s\"", channelName));
}
@ -172,4 +176,8 @@ public final class GlobalReceiverRegistry<H> {
lock.unlock();
}
}
public NetworkState getState() {
return state;
}
}

View file

@ -16,77 +16,32 @@
package net.fabricmc.fabric.impl.networking;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkState;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor;
public final class NetworkingImpl {
public static final String MOD_ID = "fabric-networking-api-v1";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
/**
* When enabled the fabric packet is written to the {@link net.minecraft.network.PacketByteBuf} on the calling thread.
* This is not enabled by default as it currently causes issues in single player.
*/
public static final boolean WRITE_FABRIC_PACKET_CALLING_THREAD = Boolean.parseBoolean(System.getProperty("fabric-api.networking.write-fabric-packet-calling-thread", "true"));
/**
* Id of packet used to register supported channels.
*/
public static final Identifier REGISTER_CHANNEL = new Identifier("minecraft", "register");
/**
* Id of packet used to unregister supported channels.
*/
public static final Identifier UNREGISTER_CHANNEL = new Identifier("minecraft", "unregister");
/**
* Id of the packet used to declare all currently supported channels.
* Dynamic registration of supported channels is still allowed using {@link NetworkingImpl#REGISTER_CHANNEL} and {@link NetworkingImpl#UNREGISTER_CHANNEL}.
*/
public static final Identifier EARLY_REGISTRATION_CHANNEL = new Identifier(MOD_ID, "early_registration");
public static void init() {
// Login setup
ServerLoginConnectionEvents.QUERY_START.register((handler, server, sender, synchronizer) -> {
// Send early registration packet
PacketByteBuf buf = PacketByteBufs.create();
Collection<Identifier> channelsNames = ServerPlayNetworking.getGlobalReceivers();
buf.writeVarInt(channelsNames.size());
for (Identifier id : channelsNames) {
buf.writeIdentifier(id);
}
sender.sendPacket(EARLY_REGISTRATION_CHANNEL, buf);
NetworkingImpl.LOGGER.debug("Sent accepted channels to the client for \"{}\"", handler.getConnectionInfo());
});
ServerLoginNetworking.registerGlobalReceiver(EARLY_REGISTRATION_CHANNEL, (server, handler, understood, buf, synchronizer, sender) -> {
if (!understood) {
// The client is likely a vanilla client.
return;
}
int n = buf.readVarInt();
List<Identifier> ids = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
ids.add(buf.readIdentifier());
}
ClientConnection connection = ((ServerLoginNetworkHandlerAccessor) handler).getConnection();
((ChannelInfoHolder) connection).getPendingChannelsNames(NetworkState.PLAY).addAll(ids);
NetworkingImpl.LOGGER.debug("Received accepted channels from the client for \"{}\"", handler.getConnectionInfo());
});
}
public static boolean isReservedPlayChannel(Identifier channelName) {
public static boolean isReservedCommonChannel(Identifier channelName) {
return channelName.equals(REGISTER_CHANNEL) || channelName.equals(UNREGISTER_CHANNEL);
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking.payload;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.login.LoginQueryRequestPayload;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
public record FabricPacketLoginQueryRequestPayload(FabricPacket fabricPacket) implements LoginQueryRequestPayload {
@Override
public void write(PacketByteBuf buf) {
fabricPacket.write(buf);
}
@Override
public Identifier id() {
return fabricPacket.getType().getId();
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking.payload;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
public record FabricPacketPayload(FabricPacket fabricPacket) implements CustomPayload {
@Override
public void write(PacketByteBuf buf) {
fabricPacket.write(buf);
}
@Override
public Identifier id() {
return fabricPacket.getType().getId();
}
}

View file

@ -24,10 +24,13 @@ import net.minecraft.network.NetworkState;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.common.PlayPingS2CPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.S2CConfigurationChannelEvents;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
@ -36,11 +39,12 @@ import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload;
import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerAccessor;
import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor;
public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetworkAddon<ServerConfigurationNetworking.ConfigurationChannelHandler> {
private final ServerConfigurationNetworkHandler handler;
private final MinecraftServer server;
private boolean sentInitialRegisterPacket;
private RegisterState registerState = RegisterState.NOT_SENT;
public ServerConfigurationNetworkAddon(ServerConfigurationNetworkHandler handler, MinecraftServer server) {
super(ServerNetworkingImpl.CONFIGURATION, ((ServerCommonNetworkHandlerAccessor) handler).getConnection(), "ServerConfigurationNetworkAddon for " + handler.getDebugProfile().getName());
@ -61,13 +65,48 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
}
}
public void sendConfiguration() {
ServerConfigurationConnectionEvents.SEND.invoker().onSendConfiguration(handler, server);
public void preConfiguration() {
ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.invoker().onSendConfiguration(handler, server);
}
public void onClientReady() {
this.sendInitialChannelRegistrationPacket();
this.sentInitialRegisterPacket = true;
public void configuration() {
ServerConfigurationConnectionEvents.CONFIGURE.invoker().onSendConfiguration(handler, server);
}
public boolean startConfiguration() {
if (this.registerState == RegisterState.NOT_SENT) {
// Send the registration packet, followed by a ping
this.sendInitialChannelRegistrationPacket();
this.sendPacket(new PlayPingS2CPacket(0xFAB71C));
this.registerState = RegisterState.SENT;
// Cancel the configuration for now, the response from the ping or registration packet will continue.
return true;
}
// We should have received a response
assert registerState == RegisterState.RECEIVED || registerState == RegisterState.NOT_RECEIVED;
return false;
}
@Override
protected void receiveRegistration(boolean register, PacketByteBuf buf) {
super.receiveRegistration(register, buf);
if (register && registerState == RegisterState.SENT) {
// We received the registration packet, thus we know this is a modded client, continue with configuration.
registerState = RegisterState.RECEIVED;
handler.sendConfigurations();
}
}
public void onPong(int parameter) {
if (registerState == RegisterState.SENT) {
// We did not receive the registration packet, thus we think this is a vanilla client, continue with configuration.
registerState = RegisterState.NOT_RECEIVED;
handler.sendConfigurations();
}
}
/**
@ -89,6 +128,7 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
@Override
protected void schedule(Runnable task) {
this.server.execute(task);
}
@Override
@ -96,18 +136,25 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
return ServerPlayNetworking.createS2CPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
return ServerPlayNetworking.createS2CPacket(packet);
}
@Override
protected void invokeRegisterEvent(List<Identifier> ids) {
S2CConfigurationChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.server, ids);
}
@Override
protected void invokeUnregisterEvent(List<Identifier> ids) {
S2CConfigurationChannelEvents.UNREGISTER.invoker().onChannelUnregister(this.handler, this, this.server, ids);
}
@Override
protected void handleRegistration(Identifier channelName) {
// If we can already send packets, immediately send the register packet for this channel
if (this.sentInitialRegisterPacket) {
if (this.registerState != RegisterState.NOT_SENT) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName));
if (buf != null) {
@ -119,7 +166,7 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
@Override
protected void handleUnregistration(Identifier channelName) {
// If we can already send packets, immediately send the unregister packet for this channel
if (this.sentInitialRegisterPacket) {
if (this.registerState != RegisterState.NOT_SENT) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName));
if (buf != null) {
@ -136,7 +183,7 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
@Override
protected boolean isReservedChannel(Identifier channelName) {
return NetworkingImpl.isReservedPlayChannel(channelName);
return NetworkingImpl.isReservedCommonChannel(channelName);
}
@Override
@ -144,4 +191,15 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
// Ensure we flush the packet.
handler.send(packet, callback, true);
}
private enum RegisterState {
NOT_SENT,
SENT,
RECEIVED,
NOT_RECEIVED
}
public ChannelInfoHolder getChannelInfoHolder() {
return (ChannelInfoHolder) ((ServerLoginNetworkHandlerAccessor) handler).getConnection();
}
}

View file

@ -40,12 +40,14 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerLoginNetworkHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking;
import net.fabricmc.fabric.impl.networking.AbstractNetworkAddon;
import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder;
import net.fabricmc.fabric.impl.networking.payload.FabricPacketLoginQueryRequestPayload;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryRequestPayload;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse;
import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor;
@ -164,9 +166,13 @@ public final class ServerLoginNetworkAddon extends AbstractNetworkAddon<ServerLo
@Override
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) {
int queryId = this.queryIdFactory.nextId();
return new LoginQueryRequestS2CPacket(queryId, new PacketByteBufLoginQueryRequestPayload(channelName, buf));
}
LoginQueryRequestS2CPacket ret = new LoginQueryRequestS2CPacket(queryId, new PacketByteBufLoginQueryRequestPayload(channelName, buf));
return ret;
@Override
public Packet<?> createPacket(FabricPacket packet) {
int queryId = this.queryIdFactory.nextId();
return new LoginQueryRequestS2CPacket(queryId, new FabricPacketLoginQueryRequestPayload(packet));
}
@Override

View file

@ -16,6 +16,9 @@
package net.fabricmc.fabric.impl.networking.server;
import java.util.Objects;
import net.minecraft.network.NetworkState;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.listener.ClientCommonPacketListener;
import net.minecraft.network.packet.Packet;
@ -25,17 +28,21 @@ import net.minecraft.server.network.ServerLoginNetworkHandler;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.FabricPacketPayload;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload;
public final class ServerNetworkingImpl {
public static final GlobalReceiverRegistry<ServerLoginNetworking.LoginQueryResponseHandler> LOGIN = new GlobalReceiverRegistry<>();
public static final GlobalReceiverRegistry<ServerConfigurationNetworking.ConfigurationChannelHandler> CONFIGURATION = new GlobalReceiverRegistry<>();
public static final GlobalReceiverRegistry<ServerPlayNetworking.PlayChannelHandler> PLAY = new GlobalReceiverRegistry<>();
public static final GlobalReceiverRegistry<ServerLoginNetworking.LoginQueryResponseHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkState.LOGIN);
public static final GlobalReceiverRegistry<ServerConfigurationNetworking.ConfigurationChannelHandler> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkState.CONFIGURATION);
public static final GlobalReceiverRegistry<ServerPlayNetworking.PlayChannelHandler> PLAY = new GlobalReceiverRegistry<>(NetworkState.PLAY);
public static ServerPlayNetworkAddon getAddon(ServerPlayNetworkHandler handler) {
return (ServerPlayNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
@ -52,4 +59,17 @@ public final class ServerNetworkingImpl {
public static Packet<ClientCommonPacketListener> createC2SPacket(Identifier channel, PacketByteBuf buf) {
return new CustomPayloadS2CPacket(new PacketByteBufPayload(channel, buf));
}
public static Packet<ClientCommonPacketListener> createC2SPacket(FabricPacket packet) {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
if (NetworkingImpl.WRITE_FABRIC_PACKET_CALLING_THREAD) {
PacketByteBuf buf = PacketByteBufs.create();
packet.write(buf);
return createC2SPacket(packet.getType().getId(), buf);
}
return new CustomPayloadS2CPacket(new FabricPacketPayload(packet));
}
}

View file

@ -27,6 +27,7 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
@ -96,6 +97,11 @@ public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<
return ServerPlayNetworking.createS2CPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
return ServerPlayNetworking.createS2CPacket(packet);
}
@Override
protected void invokeRegisterEvent(List<Identifier> ids) {
S2CPlayChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.server, ids);
@ -138,6 +144,6 @@ public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<
@Override
protected boolean isReservedChannel(Identifier channelName) {
return NetworkingImpl.isReservedPlayChannel(channelName);
return NetworkingImpl.isReservedCommonChannel(channelName);
}
}

View file

@ -22,6 +22,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket;
import net.minecraft.network.packet.c2s.common.PlayPongC2SPacket;
import net.minecraft.server.network.ServerCommonNetworkHandler;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
@ -51,4 +52,11 @@ public abstract class ServerCommonNetworkHandlerMixin implements NetworkHandlerE
}
}
}
@Inject(method = "onPlayPong", at = @At("HEAD"))
private void onPlayPong(PlayPongC2SPacket packet, CallbackInfo ci) {
if (getAddon() instanceof ServerConfigurationNetworkAddon addon) {
addon.onPong(packet.getParameter());
}
}
}

View file

@ -16,7 +16,12 @@
package net.fabricmc.fabric.mixin.networking;
import java.util.Queue;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@ -28,17 +33,42 @@ import net.minecraft.network.packet.s2c.common.DisconnectS2CPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerCommonNetworkHandler;
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.server.network.ServerPlayerConfigurationTask;
import net.minecraft.text.Text;
import net.fabricmc.fabric.api.networking.v1.FabricServerConfigurationNetworkHandler;
import net.fabricmc.fabric.impl.networking.DisconnectPacketSource;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
// We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues
@Mixin(value = ServerConfigurationNetworkHandler.class, priority = 999)
public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommonNetworkHandler implements NetworkHandlerExtensions, DisconnectPacketSource {
@Mixin(value = ServerConfigurationNetworkHandler.class, priority = 900)
public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommonNetworkHandler implements NetworkHandlerExtensions, DisconnectPacketSource, FabricServerConfigurationNetworkHandler {
@Shadow
@Nullable
private ServerPlayerConfigurationTask currentTask;
@Shadow
protected abstract void onTaskFinished(ServerPlayerConfigurationTask.Key key);
@Shadow
@Final
private Queue<ServerPlayerConfigurationTask> tasks;
@Shadow
public abstract boolean isConnectionOpen();
@Shadow
public abstract void sendConfigurations();
@Unique
ServerConfigurationNetworkAddon addon;
private ServerConfigurationNetworkAddon addon;
@Unique
private boolean sentConfiguration;
@Unique
private boolean earlyTaskExecution;
public ServerConfigurationNetworkHandlerMixin(MinecraftServer server, ClientConnection connection, int keepAliveId) {
super(server, connection, keepAliveId);
@ -51,14 +81,63 @@ public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommo
this.addon.lateInit();
}
@Inject(method = "sendConfigurations", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getCombinedDynamicRegistries()Lnet/minecraft/registry/CombinedDynamicRegistries;"))
@Inject(method = "sendConfigurations", at = @At("HEAD"), cancellable = true)
private void onClientReady(CallbackInfo ci) {
this.addon.onClientReady();
// Send the initial channel registration packet
if (this.addon.startConfiguration()) {
assert currentTask == null;
ci.cancel();
return;
}
// Ready to start sending packets
if (!sentConfiguration) {
this.addon.preConfiguration();
sentConfiguration = true;
earlyTaskExecution = true;
}
// Run the early tasks
if (earlyTaskExecution) {
if (pollEarlyTasks()) {
ci.cancel();
return;
} else {
earlyTaskExecution = false;
}
}
// All early tasks should have been completed
assert currentTask == null;
assert tasks.isEmpty();
// Run the vanilla tasks.
this.addon.configuration();
}
@Inject(method = "sendConfigurations", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerConfigurationNetworkHandler;queueSendResourcePackTask()V"))
private void sendConfigurations(CallbackInfo ci) {
this.addon.sendConfiguration();
@Unique
private boolean pollEarlyTasks() {
if (!earlyTaskExecution) {
throw new IllegalStateException("Early task execution has finished");
}
if (this.currentTask != null) {
throw new IllegalStateException("Task " + this.currentTask.getKey().id() + " has not finished yet");
}
if (!this.isConnectionOpen()) {
return false;
}
final ServerPlayerConfigurationTask task = this.tasks.poll();
if (task != null) {
this.currentTask = task;
task.sendPacket(this::sendPacket);
return true;
}
return false;
}
@Inject(method = "onDisconnected", at = @At("HEAD"))
@ -75,4 +154,26 @@ public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommo
public Packet<?> createDisconnectPacket(Text message) {
return new DisconnectS2CPacket(message);
}
@Override
public void addTask(ServerPlayerConfigurationTask task) {
tasks.add(task);
}
@Override
public void completeTask(ServerPlayerConfigurationTask.Key key) {
if (!earlyTaskExecution) {
onTaskFinished(key);
return;
}
final ServerPlayerConfigurationTask.Key currentKey = this.currentTask != null ? this.currentTask.getKey() : null;
if (!key.equals(currentKey)) {
throw new IllegalStateException("Unexpected request for task finish, current task: " + currentKey + ", requested: " + key);
}
this.currentTask = null;
sendConfigurations();
}
}

View file

@ -17,7 +17,7 @@
],
"entrypoints": {
"main": [
"net.fabricmc.fabric.impl.networking.NetworkingImpl::init"
"net.fabricmc.fabric.impl.networking.CommonPacketsImpl::init"
],
"client": [
"net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl::clientInit"
@ -37,6 +37,9 @@
}
],
"custom": {
"fabric-api:module-lifecycle": "stable"
"fabric-api:module-lifecycle": "stable",
"loom:injected_interfaces": {
"net/minecraft/class_8610": [ "net/fabricmc/fabric/api/networking/v1/FabricServerConfigurationNetworkHandler" ]
}
}
}

View file

@ -0,0 +1,335 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.networking.unit;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import net.minecraft.client.network.ClientConfigurationNetworkHandler;
import net.minecraft.network.NetworkState;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.CommonPacketHandler;
import net.fabricmc.fabric.impl.networking.CommonPacketsImpl;
import net.fabricmc.fabric.impl.networking.CommonRegisterPayload;
import net.fabricmc.fabric.impl.networking.CommonVersionPayload;
import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl;
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl;
public class CommonPacketTests {
private PacketSender packetSender;
private ChannelInfoHolder channelInfoHolder;
private ClientConfigurationNetworkHandler clientNetworkHandler;
private ClientConfigurationNetworkAddon clientAddon;
private ServerConfigurationNetworkHandler serverNetworkHandler;
private ServerConfigurationNetworkAddon serverAddon;
@BeforeAll
static void beforeAll() {
CommonPacketsImpl.init();
ClientNetworkingImpl.clientInit();
// Register a receiver to send in the play registry response
ClientPlayNetworking.registerGlobalReceiver(new Identifier("fabric", "global_client"), (client, handler, buf, responseSender) -> {
});
}
@BeforeEach
void setUp() {
packetSender = mock(PacketSender.class);
channelInfoHolder = new MockChannelInfoHolder();
clientNetworkHandler = mock(ClientConfigurationNetworkHandler.class);
clientAddon = mock(ClientConfigurationNetworkAddon.class);
when(ClientNetworkingImpl.getAddon(clientNetworkHandler)).thenReturn(clientAddon);
when(clientAddon.getChannelInfoHolder()).thenReturn(channelInfoHolder);
serverNetworkHandler = mock(ServerConfigurationNetworkHandler.class);
serverAddon = mock(ServerConfigurationNetworkAddon.class);
when(ServerNetworkingImpl.getAddon(serverNetworkHandler)).thenReturn(serverAddon);
when(serverAddon.getChannelInfoHolder()).thenReturn(channelInfoHolder);
}
// Test handling the version packet on the client
@Test
void handleVersionPacketClient() {
ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID);
assertNotNull(packetHandler);
// Receive a packet from the server
PacketByteBuf buf = PacketByteBufs.create();
buf.writeIntArray(new int[]{1, 2, 3});
packetHandler.receive(null, clientNetworkHandler, buf, packetSender);
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
// Check the response we are sending back to the server
PacketByteBuf response = readResponse(packetSender);
assertArrayEquals(new int[]{1}, response.readIntArray());
assertEquals(0, response.readableBytes());
assertEquals(1, getNegotiatedVersion(clientAddon));
}
// Test handling the version packet on the client, when the server sends unsupported versions
@Test
void handleVersionPacketClientUnsupported() {
ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID);
assertNotNull(packetHandler);
// Receive a packet from the server
PacketByteBuf buf = PacketByteBufs.create();
buf.writeIntArray(new int[]{2, 3}); // We only support version 1
assertThrows(UnsupportedOperationException.class, () -> {
packetHandler.receive(null, clientNetworkHandler, buf, packetSender);
});
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
}
// Test handling the version packet on the server
@Test
void handleVersionPacketServer() {
ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID);
assertNotNull(packetHandler);
// Receive a packet from the client
PacketByteBuf buf = PacketByteBufs.create();
buf.writeIntArray(new int[]{1, 2, 3});
packetHandler.receive(null, serverNetworkHandler, buf, null);
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
assertEquals(1, getNegotiatedVersion(serverAddon));
}
// Test handling the version packet on the server unsupported version
@Test
void handleVersionPacketServerUnsupported() {
ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID);
assertNotNull(packetHandler);
// Receive a packet from the client
PacketByteBuf buf = PacketByteBufs.create();
buf.writeIntArray(new int[]{3}); // Server only supports version 1
assertThrows(UnsupportedOperationException.class, () -> {
packetHandler.receive(null, serverNetworkHandler, buf, packetSender);
});
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
}
// Test handing the play registry packet on the client configuration handler
@Test
void handlePlayRegistryClient() {
ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID);
assertNotNull(packetHandler);
when(clientAddon.getNegotiatedVersion()).thenReturn(1);
// Receive a packet from the server
PacketByteBuf buf = PacketByteBufs.create();
buf.writeVarInt(1); // Version
buf.writeString("play"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
packetHandler.receive(null, clientNetworkHandler, buf, packetSender);
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
assertIterableEquals(List.of(new Identifier("fabric", "test")), channelInfoHolder.getPendingChannelsNames(NetworkState.PLAY));
// Check the response we are sending back to the server
PacketByteBuf response = readResponse(packetSender);
assertEquals(1, response.readVarInt());
assertEquals("play", response.readString());
assertIterableEquals(List.of(new Identifier("fabric", "global_client")), response.readCollection(HashSet::new, PacketByteBuf::readIdentifier));
assertEquals(0, response.readableBytes());
}
// Test handling the configuration registry packet on the client configuration handler
@Test
void handleConfigurationRegistryClient() {
ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID);
assertNotNull(packetHandler);
when(clientAddon.getNegotiatedVersion()).thenReturn(1);
when(clientAddon.createRegisterPayload()).thenAnswer(i -> new CommonRegisterPayload(1, "configuration", Set.of(new Identifier("fabric", "global_configuration_client"))));
// Receive a packet from the server
PacketByteBuf buf = PacketByteBufs.create();
buf.writeVarInt(1); // Version
buf.writeString("configuration"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
packetHandler.receive(null, clientNetworkHandler, buf, packetSender);
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
verify(clientAddon, times(1)).onCommonRegisterPacket(any());
// Check the response we are sending back to the server
PacketByteBuf response = readResponse(packetSender);
assertEquals(1, response.readVarInt());
assertEquals("configuration", response.readString());
assertIterableEquals(List.of(new Identifier("fabric", "global_configuration_client")), response.readCollection(HashSet::new, PacketByteBuf::readIdentifier));
assertEquals(0, response.readableBytes());
}
// Test handing the play registry packet on the server configuration handler
@Test
void handlePlayRegistryServer() {
ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID);
assertNotNull(packetHandler);
when(serverAddon.getNegotiatedVersion()).thenReturn(1);
// Receive a packet from the client
PacketByteBuf buf = PacketByteBufs.create();
buf.writeVarInt(1); // Version
buf.writeString("play"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
packetHandler.receive(null, serverNetworkHandler, buf, packetSender);
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
assertIterableEquals(List.of(new Identifier("fabric", "test")), channelInfoHolder.getPendingChannelsNames(NetworkState.PLAY));
}
// Test handing the configuration registry packet on the server configuration handler
@Test
void handleConfigurationRegistryServer() {
ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID);
assertNotNull(packetHandler);
when(serverAddon.getNegotiatedVersion()).thenReturn(1);
// Receive a packet from the client
PacketByteBuf buf = PacketByteBufs.create();
buf.writeVarInt(1); // Version
buf.writeString("configuration"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
packetHandler.receive(null, serverNetworkHandler, buf, packetSender);
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
verify(serverAddon, times(1)).onCommonRegisterPacket(any());
}
@Test
public void testHighestCommonVersionWithCommonElement() {
int[] a = {1, 2, 3};
int[] b = {1, 2};
assertEquals(2, CommonPacketsImpl.getHighestCommonVersion(a, b));
}
@Test
public void testHighestCommonVersionWithoutCommonElement() {
int[] a = {1, 3, 5};
int[] b = {2, 4, 6};
assertEquals(-1, CommonPacketsImpl.getHighestCommonVersion(a, b));
}
@Test
public void testHighestCommonVersionWithOneEmptyArray() {
int[] a = {1, 3, 5};
int[] b = {};
assertEquals(-1, CommonPacketsImpl.getHighestCommonVersion(a, b));
}
@Test
public void testHighestCommonVersionWithBothEmptyArrays() {
int[] a = {};
int[] b = {};
assertEquals(-1, CommonPacketsImpl.getHighestCommonVersion(a, b));
}
@Test
public void testHighestCommonVersionWithIdenticalArrays() {
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
assertEquals(3, CommonPacketsImpl.getHighestCommonVersion(a, b));
}
private static PacketByteBuf readResponse(PacketSender packetSender) {
ArgumentCaptor<CustomPayload> responseCaptor = ArgumentCaptor.forClass(CustomPayload.class);
verify(packetSender, times(1)).sendPacket(responseCaptor.capture());
PacketByteBuf buf = PacketByteBufs.create();
responseCaptor.getValue().write(buf);
return buf;
}
private static int getNegotiatedVersion(CommonPacketHandler packetHandler) {
ArgumentCaptor<Integer> responseCaptor = ArgumentCaptor.forClass(Integer.class);
verify(packetHandler, times(1)).onCommonVersionPacket(responseCaptor.capture());
return responseCaptor.getValue();
}
private static class MockChannelInfoHolder implements ChannelInfoHolder {
private final Map<NetworkState, Collection<Identifier>> playChannels = new ConcurrentHashMap<>();
@Override
public Collection<Identifier> getPendingChannelsNames(NetworkState state) {
return this.playChannels.computeIfAbsent(state, (key) -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
}
}
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.networking.configuration;
import java.util.function.Consumer;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.network.ServerPlayerConfigurationTask;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketType;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.test.networking.NetworkingTestmods;
/**
* Also see NetworkingConfigurationClientTest.
*/
public class NetworkingConfigurationTest implements ModInitializer {
@Override
public void onInitialize() {
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
// You must check to see if the client can handle your config task
if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.PACKET_TYPE)) {
handler.addTask(new TestConfigurationTask("Example data"));
} else {
// You can opt to disconnect the client if it cannot handle the configuration task
handler.disconnect(Text.literal("Network test configuration not supported by client"));
}
});
ServerConfigurationNetworking.registerGlobalReceiver(ConfigurationCompletePacket.PACKET_TYPE, (packet, networkHandler, responseSender) -> {
networkHandler.completeTask(TestConfigurationTask.KEY);
});
}
public record TestConfigurationTask(String data) implements ServerPlayerConfigurationTask {
public static final Key KEY = new Key(new Identifier(NetworkingTestmods.ID, "configure").toString());
@Override
public void sendPacket(Consumer<Packet<?>> sender) {
var packet = new ConfigurationPacket(data);
sender.accept(ServerConfigurationNetworking.createS2CPacket(packet));
}
@Override
public Key getKey() {
return KEY;
}
}
public record ConfigurationPacket(String data) implements FabricPacket {
public static final PacketType<ConfigurationPacket> PACKET_TYPE = PacketType.create(new Identifier(NetworkingTestmods.ID, "configure"), ConfigurationPacket::new);
public ConfigurationPacket(PacketByteBuf buf) {
this(buf.readString());
}
@Override
public void write(PacketByteBuf buf) {
buf.writeString(data);
}
@Override
public PacketType<?> getType() {
return PACKET_TYPE;
}
}
public record ConfigurationCompletePacket() implements FabricPacket {
public static final PacketType<ConfigurationCompletePacket> PACKET_TYPE = PacketType.create(new Identifier(NetworkingTestmods.ID, "configure_complete"), ConfigurationCompletePacket::new);
public ConfigurationCompletePacket(PacketByteBuf buf) {
this();
}
@Override
public void write(PacketByteBuf buf) {
}
@Override
public PacketType<?> getType() {
return PACKET_TYPE;
}
}
}

View file

@ -11,12 +11,14 @@
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.networking.channeltest.NetworkingChannelTest",
"net.fabricmc.fabric.test.networking.configuration.NetworkingConfigurationTest",
"net.fabricmc.fabric.test.networking.keybindreciever.NetworkingKeybindPacketTest",
"net.fabricmc.fabric.test.networking.login.NetworkingLoginQueryTest",
"net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketTest"
],
"client": [
"net.fabricmc.fabric.test.networking.client.channeltest.NetworkingChannelClientTest",
"net.fabricmc.fabric.test.networking.client.configuration.NetworkingConfigurationClientTest",
"net.fabricmc.fabric.test.networking.client.DisconnectScreenTest",
"net.fabricmc.fabric.test.networking.client.keybindreciever.NetworkingKeybindClientPacketTest",
"net.fabricmc.fabric.test.networking.client.login.NetworkingLoginQueryClientTest",

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.networking.client.configuration;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
import net.fabricmc.fabric.test.networking.configuration.NetworkingConfigurationTest;
public class NetworkingConfigurationClientTest implements ClientModInitializer {
@Override
public void onInitializeClient() {
ClientConfigurationNetworking.registerGlobalReceiver(NetworkingConfigurationTest.ConfigurationPacket.PACKET_TYPE, (packet, responseSender) -> {
// Handle stuff here
// Respond back to the server that the task is complete
responseSender.sendPacket(new NetworkingConfigurationTest.ConfigurationCompletePacket());
});
}
}

View file

@ -18,12 +18,15 @@ package net.fabricmc.fabric.impl.recipe.ingredient;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import io.netty.channel.ChannelHandler;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.handler.PacketEncoder;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.network.ServerPlayerConfigurationTask;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
@ -82,18 +85,12 @@ public class CustomIngredientSync implements ModInitializer {
@Override
public void onInitialize() {
ServerConfigurationConnectionEvents.SEND.register((handler, server) -> {
// TODO 1.20.2 canSend isnt working reliably during configuration
//if (!ServerConfigurationNetworking.canSend(handler, PACKET_ID)) {
// return;
//}
// Send packet with 1 so the client can send us back the list of supported tags.
// 1 is sent in case we need a different protocol later for some reason.
PacketByteBuf buf = PacketByteBufs.create();
buf.writeVarInt(PROTOCOL_VERSION_1); // max supported server protocol version
handler.sendPacket(ServerConfigurationNetworking.createS2CPacket(PACKET_ID, buf));
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
if (ServerConfigurationNetworking.canSend(handler, PACKET_ID)) {
handler.addTask(new IngredientSyncTask());
}
});
ServerConfigurationNetworking.registerGlobalReceiver(PACKET_ID, (server, handler, buf, responseSender) -> {
Set<Identifier> supportedCustomIngredients = decodeResponsePacket(buf);
ChannelHandler packetEncoder = ((ServerCommonNetworkHandlerAccessor) handler).getConnection().channel.pipeline().get("encoder");
@ -101,6 +98,26 @@ public class CustomIngredientSync implements ModInitializer {
if (packetEncoder != null) { // Null in singleplayer
((SupportedIngredientsPacketEncoder) packetEncoder).fabric_setSupportedCustomIngredients(supportedCustomIngredients);
}
handler.completeTask(IngredientSyncTask.KEY);
});
}
private record IngredientSyncTask() implements ServerPlayerConfigurationTask {
public static final Key KEY = new Key(PACKET_ID.toString());
@Override
public void sendPacket(Consumer<Packet<?>> sender) {
// Send packet with 1 so the client can send us back the list of supported tags.
// 1 is sent in case we need a different protocol later for some reason.
PacketByteBuf buf = PacketByteBufs.create();
buf.writeVarInt(PROTOCOL_VERSION_1); // max supported server protocol version
sender.accept(ServerConfigurationNetworking.createS2CPacket(PACKET_ID, buf));
}
@Override
public Key getKey() {
return KEY;
}
}
}

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.impl.client.registry.sync;
import java.util.concurrent.CompletionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -23,6 +25,8 @@ import net.minecraft.text.Text;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.impl.registry.sync.FabricRegistryInit;
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
import net.fabricmc.fabric.impl.registry.sync.RemapException;
import net.fabricmc.fabric.impl.registry.sync.packet.RegistryPacketHandler;
@ -37,20 +41,31 @@ public class FabricRegistryClientInit implements ClientModInitializer {
}
private void registerSyncPacketReceiver(RegistryPacketHandler packetHandler) {
ClientConfigurationNetworking.registerGlobalReceiver(packetHandler.getPacketId(), (client, handler, buf, responseSender) ->
RegistrySyncManager.receivePacket(client, packetHandler, buf, RegistrySyncManager.DEBUG || !client.isInSingleplayer(), (e) -> {
LOGGER.error("Registry remapping failed!", e);
client.execute(() -> ((ClientCommonNetworkHandlerAccessor) handler).getConnection().disconnect(getText(e)));
}));
ClientConfigurationNetworking.registerGlobalReceiver(packetHandler.getPacketId(), (client, handler, buf, responseSender) -> {
RegistrySyncManager.receivePacket(client, packetHandler, buf, RegistrySyncManager.DEBUG || !client.isInSingleplayer())
.whenComplete((complete, throwable) -> {
if (throwable != null) {
LOGGER.error("Registry remapping failed!", throwable);
client.execute(() -> ((ClientCommonNetworkHandlerAccessor) handler).getConnection().disconnect(getText(throwable)));
return;
}
if (complete) {
handler.sendPacket(ClientConfigurationNetworking.createC2SPacket(FabricRegistryInit.SYNC_COMPLETE_ID, PacketByteBufs.create()));
}
});
});
}
private Text getText(Exception e) {
private Text getText(Throwable e) {
if (e instanceof RemapException remapException) {
final Text text = remapException.getText();
if (text != null) {
return text;
}
} else if (e instanceof CompletionException completionException) {
return getText(completionException.getCause());
}
return Text.literal("Registry remapping failed: " + e.getMessage());

View file

@ -17,16 +17,23 @@
package net.fabricmc.fabric.impl.registry.sync;
import net.minecraft.registry.Registries;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
public class FabricRegistryInit implements ModInitializer {
public static final Identifier SYNC_COMPLETE_ID = new Identifier("fabric", "registry/sync/complete");
@Override
public void onInitialize() {
ServerConfigurationConnectionEvents.SEND.register(RegistrySyncManager::configureClient);
ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.register(RegistrySyncManager::configureClient);
ServerConfigurationNetworking.registerGlobalReceiver(SYNC_COMPLETE_ID, (server, handler, buf, responseSender) -> {
handler.completeTask(RegistrySyncManager.SyncConfigurationTask.KEY);
});
// Synced in PlaySoundS2CPacket.
RegistryAttributeHolder.get(Registries.SOUND_EVENT)

View file

@ -26,9 +26,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Consumer;
import com.google.common.base.Joiner;
@ -45,11 +44,13 @@ import org.slf4j.LoggerFactory;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.Packet;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.server.network.ServerPlayerConfigurationTask;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
@ -58,6 +59,7 @@ import net.minecraft.util.thread.ThreadExecutor;
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistryPacketHandler;
import net.fabricmc.fabric.impl.registry.sync.packet.RegistryPacketHandler;
@ -75,30 +77,48 @@ public final class RegistrySyncManager {
private RegistrySyncManager() { }
public static void configureClient(ServerConfigurationNetworkHandler handler, MinecraftServer server) {
sendPacket(server, new ConfiguringServerPlayer(handler.getDebugProfile(), handler::sendPacket));
}
static void sendPacket(MinecraftServer server, ConfiguringServerPlayer player) {
if (!DEBUG && server.isHost(player.gameProfile())) {
if (!DEBUG && server.isHost(handler.getDebugProfile())) {
// Dont send in singleplayer
return;
}
sendPacket(player, DIRECT_PACKET_HANDLER);
if (!ServerConfigurationNetworking.canSend(handler, DIRECT_PACKET_HANDLER.getPacketId())) {
// Don't send if the client cannot receive
return;
}
final Map<Identifier, Object2IntMap<Identifier>> map = RegistrySyncManager.createAndPopulateRegistryMap(true, null);
if (map == null) {
// Don't send when there is nothing to map
return;
}
handler.addTask(new SyncConfigurationTask(handler, map));
}
private static void sendPacket(ConfiguringServerPlayer player, RegistryPacketHandler handler) {
Map<Identifier, Object2IntMap<Identifier>> map = RegistrySyncManager.createAndPopulateRegistryMap(true, null);
public record SyncConfigurationTask(
ServerConfigurationNetworkHandler handler,
Map<Identifier, Object2IntMap<Identifier>> map
) implements ServerPlayerConfigurationTask {
public static final Key KEY = new Key("fabric:registry/sync");
if (map != null) {
handler.sendPacket(player, map);
@Override
public void sendPacket(Consumer<Packet<?>> sender) {
DIRECT_PACKET_HANDLER.sendPacket(handler::sendPacket, map);
}
@Override
public Key getKey() {
return KEY;
}
}
public static void receivePacket(ThreadExecutor<?> executor, RegistryPacketHandler handler, PacketByteBuf buf, boolean accept, Consumer<Exception> errorHandler) {
public static CompletableFuture<Boolean> receivePacket(ThreadExecutor<?> executor, RegistryPacketHandler handler, PacketByteBuf buf, boolean accept) {
handler.receivePacket(buf);
if (!handler.isPacketFinished()) {
return;
return CompletableFuture.completedFuture(false);
}
if (DEBUG) {
@ -110,26 +130,22 @@ public final class RegistrySyncManager {
Map<Identifier, Object2IntMap<Identifier>> map = handler.getSyncedRegistryMap();
if (accept) {
try {
executor.submit(() -> {
if (map == null) {
errorHandler.accept(new RemapException("Received null map in sync packet!"));
return null;
}
try {
apply(map, RemappableRegistry.RemapMode.REMOTE);
} catch (RemapException e) {
errorHandler.accept(e);
}
return null;
}).get(30, TimeUnit.SECONDS);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
errorHandler.accept(e);
}
if (!accept) {
return CompletableFuture.completedFuture(true);
}
return executor.submit(() -> {
if (map == null) {
throw new CompletionException(new RemapException("Received null map in sync packet!"));
}
try {
apply(map, RemappableRegistry.RemapMode.REMOTE);
return true;
} catch (RemapException e) {
throw new CompletionException(e);
}
});
}
/**

View file

@ -22,6 +22,7 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
@ -30,10 +31,10 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.impl.registry.sync.ConfiguringServerPlayer;
/**
* A more optimized method to sync registry ids to client.
@ -73,7 +74,7 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler {
}
@Override
public void sendPacket(ConfiguringServerPlayer player, Map<Identifier, Object2IntMap<Identifier>> registryMap) {
public void sendPacket(Consumer<Packet<?>> sender, Map<Identifier, Object2IntMap<Identifier>> registryMap) {
PacketByteBuf buf = PacketByteBufs.create();
// Group registry ids with same namespace.
@ -152,12 +153,12 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler {
while (sliceIndex < readableBytes) {
int sliceSize = Math.min(readableBytes - sliceIndex, MAX_PAYLOAD_SIZE);
PacketByteBuf slicedBuf = PacketByteBufs.slice(buf, sliceIndex, sliceSize);
sendPacket(player, slicedBuf);
sendPacket(sender, slicedBuf);
sliceIndex += sliceSize;
}
// Send an empty buffer to mark the end of the split.
sendPacket(player, PacketByteBufs.empty());
sendPacket(sender, PacketByteBufs.empty());
}
@Override

View file

@ -17,6 +17,7 @@
package net.fabricmc.fabric.impl.registry.sync.packet;
import java.util.Map;
import java.util.function.Consumer;
import java.util.zip.Deflater;
import io.netty.buffer.ByteBuf;
@ -24,10 +25,11 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.impl.registry.sync.ConfiguringServerPlayer;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
public abstract class RegistryPacketHandler {
@ -36,7 +38,7 @@ public abstract class RegistryPacketHandler {
public abstract Identifier getPacketId();
public abstract void sendPacket(ConfiguringServerPlayer player, Map<Identifier, Object2IntMap<Identifier>> registryMap);
public abstract void sendPacket(Consumer<Packet<?>> sender, Map<Identifier, Object2IntMap<Identifier>> registryMap);
public abstract void receivePacket(PacketByteBuf buf);
@ -47,8 +49,8 @@ public abstract class RegistryPacketHandler {
@Nullable
public abstract Map<Identifier, Object2IntMap<Identifier>> getSyncedRegistryMap();
protected final void sendPacket(ConfiguringServerPlayer player, PacketByteBuf buf) {
player.sendPacket(getPacketId(), buf);
protected final void sendPacket(Consumer<Packet<?>> sender, PacketByteBuf buf) {
sender.accept(ServerConfigurationNetworking.createS2CPacket(getPacketId(), buf));
}
protected final void computeBufSize(PacketByteBuf buf) {