Switch to using the simple networking apis provided by fabwork

This commit is contained in:
Sollace 2022-12-29 22:37:10 +01:00
parent 59dc4d53b9
commit 1763f433d4
28 changed files with 35 additions and 239 deletions

View file

@ -46,7 +46,7 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable {
private <T extends Hit> boolean triggerQuickAction(Ability<T> ability, ActivationType pressType) {
Optional<T> data = ability.prepareQuickAction(player, pressType);
if (ability.onQuickAction(player, pressType, data)) {
Channel.CLIENT_PLAYER_ABILITY.send(new MsgPlayerAbility<>(ability, data, pressType));
Channel.CLIENT_PLAYER_ABILITY.sendToServer(new MsgPlayerAbility<>(ability, data, pressType));
return true;
}
return false;
@ -209,7 +209,7 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable {
Optional<T> data = ability.prepare(player);
if (data.isPresent()) {
Channel.CLIENT_PLAYER_ABILITY.send(new MsgPlayerAbility<>(ability, data, ActivationType.NONE));
Channel.CLIENT_PLAYER_ABILITY.sendToServer(new MsgPlayerAbility<>(ability, data, ActivationType.NONE));
} else {
player.asEntity().playSound(USounds.GUI_ABILITY_FAIL, 1, 1);
setCooldown(0);

View file

@ -52,7 +52,7 @@ public class TraitDiscovery implements NbtSerialisable {
@Environment(EnvType.CLIENT)
public void markRead(Trait trait) {
Channel.MARK_TRAIT_READ.send(new MsgMarkTraitRead(Set.of(trait)));
Channel.MARK_TRAIT_READ.sendToServer(new MsgMarkTraitRead(Set.of(trait)));
}
public void markRead(Set<Trait> traits) {
@ -76,7 +76,7 @@ public class TraitDiscovery implements NbtSerialisable {
unreadTraits.addAll(newTraits);
pony.setDirty();
if (!newTraits.isEmpty() && !pony.asWorld().isClient) {
Channel.UNLOCK_TRAITS.send((ServerPlayerEntity)pony.asEntity(), new MsgUnlockTraits(newTraits));
Channel.UNLOCK_TRAITS.sendToPlayer(new MsgUnlockTraits(newTraits), (ServerPlayerEntity)pony.asEntity());
}
}

View file

@ -90,7 +90,7 @@ public class BlockDestructionManager implements Tickable {
players.forEach(player -> {
if (player instanceof ServerPlayerEntity) {
Channel.SERVER_BLOCK_DESTRUCTION.send(player, msg);
Channel.SERVER_BLOCK_DESTRUCTION.sendToPlayer(msg, player);
}
});
}

View file

@ -146,7 +146,7 @@ public class DismissSpellScreen extends GameGui {
if (isMouseOver(relativeMouseX, relativeMouseY)) {
remove(this);
pony.getSpellSlot().removeIf(spell -> spell == this.spell, true);
Channel.REMOVE_SPELL.send(new MsgRemoveSpell(spell));
Channel.REMOVE_SPELL.sendToServer(new MsgRemoveSpell(spell));
playClickEffect();
return true;
}

View file

@ -78,7 +78,7 @@ public class TribeSelectionScreen extends GameGui implements HidesHud {
finished = false;
if (result) {
Channel.CLIENT_REQUEST_SPECIES_CHANGE.send(new MsgRequestSpeciesChange(race, true));
Channel.CLIENT_REQUEST_SPECIES_CHANGE.sendToServer(new MsgRequestSpeciesChange(race, true));
finish();
} else {
client.setScreen(this);

View file

@ -70,7 +70,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
return chapters.getCurrentChapter() == craftingChapter;
});
handler.getSpellbookState().setSynchronizer(state -> {
Channel.CLIENT_SPELLBOOK_UPDATE.send(new MsgSpellbookStateChanged<ServerPlayerEntity>(handler.syncId, state));
Channel.CLIENT_SPELLBOOK_UPDATE.sendToServer(new MsgSpellbookStateChanged<ServerPlayerEntity>(handler.syncId, state));
});
}

View file

@ -55,9 +55,7 @@ public class SpellbookChapterLoader extends JsonDataLoader implements Identifiab
dirty = false;
MsgServerResources msg = new MsgServerResources();
server.getWorlds().forEach(world -> {
world.getPlayers().forEach(player -> {
Channel.SERVER_RESOURCES_SEND.send(player, msg);
});
Channel.SERVER_RESOURCES_SEND.sendToAllPlayers(msg, world);
});
}
}

View file

@ -58,7 +58,7 @@ public class SpellbookEntity extends MobEntity {
if (player instanceof ServerPlayerEntity recipient
&& player.currentScreenHandler instanceof SpellbookScreenHandler book
&& getUuid().equals(book.entityId)) {
Channel.SERVER_SPELLBOOK_UPDATE.send(recipient, new MsgSpellbookStateChanged<>(player.currentScreenHandler.syncId, state));
Channel.SERVER_SPELLBOOK_UPDATE.sendToPlayer(new MsgSpellbookStateChanged<>(player.currentScreenHandler.syncId, state), recipient);
}
});
});

View file

@ -131,7 +131,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
this.animationMaxDuration = animationDuration;
if (!isClient()) {
Channel.SERVER_PLAYER_ANIMATION_CHANGE.send(asWorld(), new MsgPlayerAnimationChange(this, animation, animationDuration));
Channel.SERVER_PLAYER_ANIMATION_CHANGE.sendToAllPlayers(new MsgPlayerAnimationChange(this, animation, animationDuration), asWorld());
}
animation.getSound().ifPresent(sound -> {
@ -243,8 +243,8 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
if (entity instanceof ServerPlayerEntity) {
MsgOtherPlayerCapabilities packet = new MsgOtherPlayerCapabilities(this);
Channel.SERVER_PLAYER_CAPABILITIES.send((ServerPlayerEntity)entity, packet);
Channel.SERVER_OTHER_PLAYER_CAPABILITIES.send(entity.world, packet);
Channel.SERVER_PLAYER_CAPABILITIES.sendToPlayer(packet, (ServerPlayerEntity)entity);
Channel.SERVER_OTHER_PLAYER_CAPABILITIES.sendToSurroundingPlayers(packet, entity);
}
}

View file

@ -1,16 +1,12 @@
package com.minelittlepony.unicopia.network;
import com.minelittlepony.unicopia.util.network.S2CBroadcastPacketType;
import com.minelittlepony.unicopia.util.network.S2CPacketType;
import com.minelittlepony.unicopia.*;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.network.C2SPacketType;
import com.minelittlepony.unicopia.util.network.SimpleNetworking;
import com.sollace.fabwork.api.packets.*;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
public interface Channel {
C2SPacketType<MsgPlayerAbility<?>> CLIENT_PLAYER_ABILITY = SimpleNetworking.clientToServer(Unicopia.id("player_ability"), MsgPlayerAbility::new);
@ -24,17 +20,15 @@ public interface Channel {
S2CPacketType<MsgCancelPlayerAbility> CANCEL_PLAYER_ABILITY = SimpleNetworking.serverToClient(Unicopia.id("player_ability_cancel"), MsgCancelPlayerAbility::new);
S2CPacketType<MsgUnlockTraits> UNLOCK_TRAITS = SimpleNetworking.serverToClient(Unicopia.id("unlock_traits"), MsgUnlockTraits::new);
Identifier SERVER_SELECT_TRIBE_ID = Unicopia.id("select_tribe");
S2CPacketType<MsgTribeSelect> SERVER_SELECT_TRIBE = SimpleNetworking.serverToClient(SERVER_SELECT_TRIBE_ID, MsgTribeSelect::new);
S2CPacketType<MsgTribeSelect> SERVER_SELECT_TRIBE = SimpleNetworking.serverToClient(Unicopia.id("select_tribe"), MsgTribeSelect::new);
S2CPacketType<MsgSpellbookStateChanged<PlayerEntity>> SERVER_SPELLBOOK_UPDATE = SimpleNetworking.serverToClient(Unicopia.id("server_spellbook_update"), MsgSpellbookStateChanged::new);
C2SPacketType<MsgSpellbookStateChanged<ServerPlayerEntity>> CLIENT_SPELLBOOK_UPDATE = SimpleNetworking.clientToServer(Unicopia.id("client_spellbook_update"), MsgSpellbookStateChanged::new);
Identifier SERVER_RESOURCES_SEND_ID = Unicopia.id("resources_send");
S2CPacketType<MsgServerResources> SERVER_RESOURCES_SEND = SimpleNetworking.serverToClient(SERVER_RESOURCES_SEND_ID, MsgServerResources::new);
S2CPacketType<MsgServerResources> SERVER_RESOURCES_SEND = SimpleNetworking.serverToClient(Unicopia.id("resources_send"), MsgServerResources::new);
S2CBroadcastPacketType<MsgOtherPlayerCapabilities> SERVER_OTHER_PLAYER_CAPABILITIES = SimpleNetworking.serverToClients(Unicopia.id("other_player_capabilities"), MsgOtherPlayerCapabilities::new);
S2CBroadcastPacketType<MsgPlayerAnimationChange> SERVER_PLAYER_ANIMATION_CHANGE = SimpleNetworking.serverToClients(Unicopia.id("other_player_animation_change"), MsgPlayerAnimationChange::new);
S2CPacketType<MsgOtherPlayerCapabilities> SERVER_OTHER_PLAYER_CAPABILITIES = SimpleNetworking.serverToClient(Unicopia.id("other_player_capabilities"), MsgOtherPlayerCapabilities::new);
S2CPacketType<MsgPlayerAnimationChange> SERVER_PLAYER_ANIMATION_CHANGE = SimpleNetworking.serverToClient(Unicopia.id("other_player_animation_change"), MsgPlayerAnimationChange::new);
static void bootstrap() {
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
@ -48,10 +42,10 @@ public interface Channel {
pony.setSpecies(race);
Unicopia.LOGGER.info("Setting {}'s race to {} due to host setting", handler.player.getDisplayName().getString(), Race.REGISTRY.getId(race).toString());
} else {
sender.sendPacket(SERVER_SELECT_TRIBE.getId(), new MsgTribeSelect(handler.player).toBuffer());
sender.sendPacket(SERVER_SELECT_TRIBE.id(), new MsgTribeSelect(handler.player).toBuffer());
}
}
sender.sendPacket(SERVER_RESOURCES_SEND.getId(), new MsgServerResources().toBuffer());
sender.sendPacket(SERVER_RESOURCES_SEND.id(), new MsgServerResources().toBuffer());
});
}
}

View file

@ -1,7 +1,7 @@
package com.minelittlepony.unicopia.network;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;

View file

@ -1,7 +1,7 @@
package com.minelittlepony.unicopia.network;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;

View file

@ -5,7 +5,7 @@ import java.util.Set;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;

View file

@ -7,7 +7,7 @@ import com.minelittlepony.unicopia.ability.Ability;
import com.minelittlepony.unicopia.ability.ActivationType;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;
@ -52,7 +52,7 @@ public class MsgPlayerAbility<T extends Hit> implements Packet<ServerPlayerEntit
} else {
data.filter(data -> power.canApply(player, data)).ifPresentOrElse(
data -> power.apply(player, data),
() -> Channel.CANCEL_PLAYER_ABILITY.send(sender, new MsgCancelPlayerAbility())
() -> Channel.CANCEL_PLAYER_ABILITY.sendToPlayer(new MsgCancelPlayerAbility(), sender)
);
}
}

View file

@ -4,7 +4,7 @@ import java.util.UUID;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.player.PlayerEntity;

View file

@ -7,7 +7,7 @@ import java.util.UUID;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;

View file

@ -4,7 +4,7 @@ import java.util.UUID;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;

View file

@ -3,7 +3,7 @@ package com.minelittlepony.unicopia.network;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.WorldTribeManager;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;
@ -53,6 +53,6 @@ public class MsgRequestSpeciesChange implements Packet<ServerPlayerEntity> {
}
}
Channel.SERVER_PLAYER_CAPABILITIES.send(sender, new MsgPlayerCapabilities(player));
Channel.SERVER_PLAYER_CAPABILITIES.sendToPlayer(new MsgPlayerCapabilities(player), sender);
}
}

View file

@ -6,7 +6,7 @@ import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.ability.data.tree.TreeTypeLoader;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.container.SpellbookChapterLoader;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;

View file

@ -4,7 +4,7 @@ import java.util.Optional;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Owned;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;

View file

@ -2,7 +2,7 @@ package com.minelittlepony.unicopia.network;
import com.minelittlepony.unicopia.container.SpellbookScreenHandler;
import com.minelittlepony.unicopia.container.SpellbookState;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;

View file

@ -5,7 +5,7 @@ import java.util.Set;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;

View file

@ -5,7 +5,7 @@ import java.util.Set;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.util.network.Packet;
import com.sollace.fabwork.api.packets.Packet;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.entity.player.PlayerEntity;

View file

@ -1,21 +0,0 @@
package com.minelittlepony.unicopia.util.network;
import com.google.common.base.Preconditions;
import net.fabricmc.api.EnvType;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
/**
* A server packet type. Sent by the client to the server.
*/
public interface C2SPacketType<T extends Packet<ServerPlayerEntity>> {
Identifier getId();
default void send(T packet) {
Preconditions.checkState(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT, "Client packet send called by the server");
ClientPlayNetworking.send(getId(), packet.toBuffer());
}
}

View file

@ -1,35 +0,0 @@
package com.minelittlepony.unicopia.util.network;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
/**
* Represents a message that can be either send from the client to the server or back.
*/
public interface Packet<P extends PlayerEntity> {
/**
* Called to handle this packet on the receiving end.
*
* @param sender The player who initially sent this packet.
*/
void handle(P sender);
/**
* Writes this packet to the supplied buffer prior to transmission.
*
* @param buffer The buffer to write to.
*/
void toBuffer(PacketByteBuf buffer);
/**
* Writes this packet to a new buffer.
*
* @return The resulting buffer for transmission
*/
default PacketByteBuf toBuffer() {
PacketByteBuf buf = PacketByteBufs.create();
toBuffer(buf);
return buf;
}
}

View file

@ -1,22 +0,0 @@
package com.minelittlepony.unicopia.util.network;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.world.World;
/**
* A broadcast packet type. Sent by the server to all surrounding players.
*/
public interface S2CBroadcastPacketType<T extends Packet<PlayerEntity>> {
Identifier getId();
default void send(World world, T packet) {
world.getPlayers().forEach(player -> {
if (player instanceof ServerPlayerEntity) {
ServerPlayNetworking.send((ServerPlayerEntity)player, getId(), packet.toBuffer());
}
});
}
}

View file

@ -1,22 +0,0 @@
package com.minelittlepony.unicopia.util.network;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.listener.ClientPlayPacketListener;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
/**
* A client packet type. Sent by the server to a specific player.
*/
public interface S2CPacketType<T extends Packet<? extends PlayerEntity>> {
Identifier getId();
default void send(ServerPlayerEntity recipient, T packet) {
ServerPlayNetworking.send(recipient, getId(), packet.toBuffer());
}
default net.minecraft.network.Packet<ClientPlayPacketListener> toPacket(T packet) {
return ServerPlayNetworking.createS2CPacket(getId(), packet.toBuffer());
}
}

View file

@ -1,96 +0,0 @@
package com.minelittlepony.unicopia.util.network;
import java.util.function.Function;
import net.fabricmc.api.EnvType;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
/**
* A simplified, side-agnostic, and declaritive wrapper around {@link ServerPlayNetworking} and {@link ClientPlayNetworking}
* designed to bring networking in line with the declaritive/registered nature of other parts of Mojang's echosystem.
* <p>
* It is safe to call these methods from either the client or the server, so modders can implement a
* single static <code>PacketTypes</code> class with which they can easily send packets without worrying
* about the complexities of the network thread, which side to register a global receiver on,
* which method to use to send, or even whether their receiver is registered for a given player or not.
* <p>
* All of the above is handled in a black-box style by this class.
* <p>
* <ul>
* <li>Packets are automatically registered on the appropriate sides.</li>
* <li>Sending is done in the same way by calling `send` on your packet type.</li>
* <li>Your packet's <code>handle</code> method is executed on the main thread where it is safe to interact with the world.</li>
*/
public final class SimpleNetworking {
private SimpleNetworking() {throw new RuntimeException("new SimpleNetworking()");}
/**
* Registers a packet type for transmisison to the server.
* <p>
* The returned handle can be used by the client to send messages to the active minecraft server.
* <p>
*
* @param <T> The type of packet to implement
* @param id The message's unique used for serialization
* @param factory A constructor returning new instances of the packet type
*
* @return A registered PacketType
*/
public static <T extends Packet<ServerPlayerEntity>> C2SPacketType<T> clientToServer(Identifier id, Function<PacketByteBuf, T> factory) {
ServerPlayNetworking.registerGlobalReceiver(id, (server, player, handler, buffer, responder) -> {
T packet = factory.apply(buffer);
server.execute(() -> packet.handle(player));
});
return () -> id;
}
/**
* Registers a packet type for transmission to the client.
*
* The returned handle can be used by the server to send messages to a given recipient.
*
* @param <T> The type of packet to implement
* @param id The message's unique used for serialization
* @param factory A constructor returning new instances of the packet type
*
* @return A registered PacketType
*/
public static <T extends Packet<PlayerEntity>> S2CPacketType<T> serverToClient(Identifier id, Function<PacketByteBuf, T> factory) {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
ClientProxy.register(id, factory);
}
return () -> id;
}
/**
* Registers a packet type for transmission to all clients.
*
* The returned handle can be used by the server to broadcast a message to all connected clients in a given dimension.
*
* @param <T> The type of packet to implement
* @param id The message's unique used for serialization
* @param factory A constructor returning new instances of the packet type
*
* @return A registered PacketType
*/
public static <T extends Packet<PlayerEntity>> S2CBroadcastPacketType<T> serverToClients(Identifier id, Function<PacketByteBuf, T> factory) {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
ClientProxy.register(id, factory);
}
return () -> id;
}
// Fabric's APIs are not side-agnostic.
// We punt this to a separate class file to keep it from being eager-loaded on a server environment.
private static final class ClientProxy {
private ClientProxy() {throw new RuntimeException("new ClientProxy()");}
public static <T extends Packet<PlayerEntity>> void register(Identifier id, Function<PacketByteBuf, T> factory) {
ClientPlayNetworking.registerGlobalReceiver(id, (client, ignore1, buffer, ignore2) -> {
T packet = factory.apply(buffer);
client.execute(() -> packet.handle(client.player));
});
}
}
}