Иконка ресурса

PacketAPI 1.0.1

Нет прав для скачивания
Версия(и) Minecraft
1.12.2, 1.7.10
PacketAPI призвано упростить работу с пакетами. Кратко:
  • Облегчите себе жизнь и избавьтесь от создания обработчика для каждого пакета, при этом сохраняя ООП стиль и синхронность обработки
  • Регистрируйте пакеты без надобности указывать дескриминаторы и прочий блудняк
  • Колбэки. Отсылайте запрос серверу и ждите ответа асинхронно, а затем обрабатывайте результат синхронно и все это в одной строчке кода
  • Контроль над пакетами. Подавляйте спам пакетами одной аннотацией
  • Используйте удобные PacketHandler'ы, содержащие набор популярных методов для отправки данных туда и сюда
  • Конвертируйте Object <-> byte[] без боли
  • Работайте как с ForgeClient <-> ForgeServer, так и ForgeClient <-> BukkitAPI
  • Ленивая отправка данных без пакетов

Примером применения апи может стать любой мод. На своем опыте, могу сказать, что апи сильно упрощает работу с любыми модами.
Тек, колбэки ускорят разработку клиент-серверных систем в разы, ленивая отправка позволит отсылать данные о клиентских ивентах на сервер в несколько строчек кода, а поддержка BukkitAPI позволит легко связать моды с плагинами или перевести ваши моды в плагины.

пример использования колбэков:
packetHandler.sendCallback(new MyMathQuestion("2 * 2 = 4 ?"))
    .onResult(result -> {
        if(result.isTrue()) {
            sout("Все верно");
        } else {
            sout("Не верно");
        }
    });
IO:
// запись
void write(EntityPlayerMP entityPlayer, ByteBufOutputStream bbos) throws IOException {
    ShopCategory category = new ShopCategory();
    for (int i = 0; i < ; i++) {
        category.add(new ShopItem("название", 1000));
    }
    writeObject(bbos, category);
}
// чтение
void read(ByteBufInputStream bbis) throws IOException {
    ShopCategory category = readObject(bbis, ShopCategory.class);
}

Здесь будет рассмотрен пример простой игры в угадайку числа. Клиент будет генерировать случайное число при отправке запроса серверу. Сервер же будет решать, угадал клиент или нет и отсылать ему ответ.
Пример также продемонстрирует возможность применения @ControllablePacket(period = 200L) , что фактически эквивалентно строке private static final RequestController<UUID> REQUEST_CONTROLLER = new RequestController.Periodic<>(200L);.

RequestController.Periodic даст возможность блокировать обработку пакета, если клиент слишком часто шлет пакеты этого типа. В данном примере, пакет от клиента будет обработан на сервере только в том случае, если период отправки двух одинаковых пакетов клиентом не превышает 200 миллисекунд. В обратном случае, мы все равно вызовем метод write у серверного пакета и уведомим клиента, что он пренебрегает возможностью угадать число.
Клиент-серверный мод:
@Mod(modid = MODID)
public class MyTestMod {

    static final String MODID = "packetapiexample";

    private PacketHandlerClient packetHandlerClient;
    private PacketHandlerServer packetHandlerServer;

    @Mod.EventHandler
    public void event(FMLInitializationEvent event) {
        if(event.getSide() == Side.CLIENT) {
            packetHandlerClient = new PacketHandlerClient(new SimplePacketRegistry().register(0, new MyTestCallback()), MODID);
            MinecraftForge.EVENT_BUS.register(this);
        } else {
            packetHandlerServer = new PacketHandlerServer(new SimplePacketRegistry().register(0, new MyTestCallbackOnServer()), MODID);
        }
    }

    @SubscribeEvent
    public void event(InputEvent.KeyInputEvent event) {
        if(Keyboard.getEventKeyState() && Keyboard.isKeyDown(Keyboard.KEY_P)) {
            int v = new Random().nextInt(10);
            System.out.println(String.format("Sending a number о %s for verification to the server side...", v));
            packetHandlerClient.sendPacketEffectiveCallback(new MyTestCallback(v))
                    .thenAcceptSync(result -> {
                        if (result.isSuccess()) {
                            System.out.println("Success!");
                        } else {
                            System.out.println("Failure!");
                        }
                        System.out.println(result.getResponseMessage());
                    });
        }
    }
}
Клиентский пакет:
public class MyTestCallback implements IPacketCallbackEffective<MyTestCallback.Result> {

    private final Result result = new Result();

    private int value;

    public MyTestCallback() {}

    public MyTestCallback(int value) {
        this.value = value;
    }

    @Override
    public void write(ByteBufOutputStream bbos) throws IOException {
        bbos.writeInt(value);
    }

    @Override
    public void read(ByteBufInputStream bbis) throws IOException {
        result.success = bbis.readBoolean();
        result.responseMessage = bbis.readUTF();
    }

    @Nullable
    @Override
    public Result getResult() {
        return result;
    }

    public static class Result {

        private boolean success;
        private String responseMessage;

        public boolean isSuccess() {
            return success;
        }

        public String getResponseMessage() {
            return responseMessage;
        }
    }
}
Серверный пакет:
@ControllablePacket(period = 200L, callWriteAnyway = true)
public class MyTestCallbackOnServer implements IPacketCallbackOnServer {

    private Integer value;

    @Override
    public void read(EntityPlayerMP entityPlayer, ByteBufInputStream bbis, PacketCallbackSender packetCallbackSender) throws IOException {
        System.out.println("Входящий запрос от " + entityPlayer.getName());
        value = bbis.readInt();
    }

    @Override
    public void write(EntityPlayerMP entityPlayer, ByteBufOutputStream bbos) throws IOException {
        System.out.println("Отсылаю результат...");
        boolean success = false;
        String message = "Вы слишком часто отсылали запрос. Запрос отклонен!";
        if (value != null) {
            message = "Вы не угадали секретное число, это точно не 4!";
            if(value == 4) {
                success = true;
                message = "Вы угадали секретное число, это действительно 4!";
            }
        }
        bbos.writeBoolean(success);
        bbos.writeUTF(message);
        System.out.println("Результат отослан.");
    }
}
У нас есть задача передать клиенту все товары всех категорий магазина. Мы можем сделать это двумя способами:
  1. Отослать все сразу при коннекте клиента к серверу, что будет неэффективно, по крайней мере, по одной причине - на клиента может упасть сразу очень много информации, что возможно может перегрузить его на некоторое время.
  2. Отсылать каждую из категорий по мере ее загрузки в интерфейсе. В этом же случае нам придется писать слишком сложную систему для такой просто задачи. Придется организовывать структуру пакетов, писать для каждого обработчик, придумывать как загрузить пришедшую информацию по категории в интерфейс и т.д.
Я вижу второй вариант практичнее, но означает ли это, что придется столкнуться со всем гемороем, описанным выше ? Нет, здесь и пригодится PacketAPI. Апи поддерживает нехитрую систему колбэков, которые объединяют 2 пакета в один и не вынуждают писать для этих пакетов обработчики, а позволяют проворачивать запись и чтение в 2 методах. Мало того, колбэки позволяют описать обработку результата в одну строчку кода прямо в моменте своей отправки. Наглядно:
Момент отправки пакета:
System.out.println("Sending a request to the server for shop items by category: " + category.toString());
packetHandlerClient.sendCallback(new PacketShopCategoryGet(category.toString()))
        .onResult(result -> {
            // it will happen when a callback will return back with the result to the client side
            System.out.println("The incoming set of shop items:");
            result.getShopItemList().forEach(System.out::println);
            System.out.println("The message from the server: " + result.getResponseMessage());
        })
        .onTimeout(() -> System.out.println("timeout"))
        .onException(() -> System.out.println("exception"));
Клиентский пакет-запрос:
public class PacketShopCategoryGet implements IPacketCallbackEffective<PacketShopCategoryGet.Result> {

    private final Result result = new Result();

    private String category;

    public PacketShopCategoryGet() {}

    public PacketShopCategoryGet(String category) {
        this.category = category;
    }

    @Override
    public void write(ByteBufOutputStream bbos) throws IOException {
        bbos.writeUTF(category);
    }

    @Override
    public void read(ByteBufInputStream bbis) throws IOException {
        result.shopItemList = readObjects(bbis, ShopItem.class);
        result.responseMessage = bbis.readUTF();
    }

    @Nullable
    @Override
    public Result getResult() {
        return result;
    }

    public static class Result {
        private String responseMessage;
        private List<ShopItem> shopItemList;

        public String getResponseMessage() {
            return responseMessage;
        }

        public List<ShopItem> getShopItemList() {
            return shopItemList;
        }
    }
}
Серверный пакет-обработчик:
public class PacketShopCategoryGetOnServer implements IPacketCallbackOnServer {

    private static final RequestController<UUID> REQUEST_CONTROLLER = new RequestController.Periodic<>(1000L);

    private List<ShopItem> shopItems;
    private long period;

    @Override
    public void read(EntityPlayerMP entityPlayer, ByteBufInputStream bbis, PacketCallbackSender packetCallbackSender) throws IOException {
        String category = bbis.readUTF();
        System.out.println("Входящий запрос товаров по категории: " + category);
        long l = System.currentTimeMillis();
        REQUEST_CONTROLLER.doCompletedRequestAsync(entityPlayer.getUniqueID(), () -> {
            shopItems = ShopMod.INSTANCE.getShopItemManager().getItemListByCategory(category);
            period = System.currentTimeMillis() - l;
            System.out.println("Запрос обработан. Отсылаю ответ...");
            packetCallbackSender.send();
        });
    }

    @Override
    public void write(EntityPlayerMP entityPlayer, ByteBufOutputStream bbos) throws IOException {
        writeObjects(bbos, shopItems);
        bbos.writeUTF("Обработка запроса заняла " + period + " миллисекунд");
    }

    @Override
    public boolean handleCallback() {
        return true;
    }
}
Если вы пишете серверную часть на BukkitAPI, будь то Sponge или Spigot, вы все также можете использовать PacketAPI. Апи предоставляет два набора серверных инструментов для работы с пакетами: для Forge и для BukkitAPI.

Данный пример демонстрирует работу между BukkitAPI и Forge. Здесь решается задача вывода в консоль клиенту времени с момента логина, которое хранится исключительно на сервере.
Клиентский мод:
@Mod(modid = "timesenderexample")
public class OnlineTimeSenderMod {
    private PacketHandlerClient packetHandlerClient = new PacketHandlerClient(new SimplePacketRegistry().register(new PacketOnlineReceive()), "timesenderexample");
}
Плагин на BukkitAPI:
public class OnlineTimeSenderPlugin extends JavaPlugin implements Listener {

    private PacketHandlerBukkitServer packetHandlerBukkitServer;
    public static final TObjectLongMap<UUID> ONLINE_MAP = new TObjectLongHashMap<>();

    @Override
    public void onEnable() {
        packetHandlerBukkitServer = new PacketHandlerBukkitServer(this, new SimplePacketRegistry().register(new PacketOnlineSend()), "timesenderexample");
        getServer().getPluginManager().registerEvents(this, this);
        getServer().getScheduler().scheduleSyncRepeatingTask(this, () -> packetHandlerBukkitServer.sendPacketToAll(new PacketOnlineSend()), 0L, 20L);
    }

    @EventHandler
    public void event(PlayerLoginEvent event) {
        System.out.println("Logged in " + event.getPlayer().getName());
        ONLINE_MAP.put(event.getPlayer().getUniqueId(), System.currentTimeMillis());
    }
}
Серверный пакет, отправляющий нам количество секунд, прошедшее с нашего логина на сервера:
public class PacketOnlineSend implements IPacketOutBukkit {
    @Override
    public void write(Player player, ByteBufOutputStream bbos) throws IOException {
        bbos.writeInt((int) ((System.currentTimeMillis() - OnlineTimeSenderPlugin.ONLINE_MAP.get(player.getUniqueId())) / 1000));
        bbos.writeUTF("I'm a string, just a string");
    }
}
Клиентский пакет, получающий информацию с сервера и принтящий ее в консоль:
public class PacketOnlineReceive implements IPacketIn {
    @Override
    public void read(ByteBufInputStream bbis) throws IOException {
        int seconds = bbis.readInt();
        System.out.println("Your actual played time: " + seconds + " sec");
        System.out.println(bbis.readUTF());
    }
}
Composable - особый тип объектов в рамках PacketAPI. По сути, это простой Serializable, что освобождает разработчика от муторного описания процесса компоновки данных из объекта в буфер и обратно.
Composable объекты как и Serializable поддерживают вложенность, это гарантирует, что Composable в Composable, в котором еще один Composable будет записан одной строчкой кода в буфер.
Java:
public class MyNode implements Composable {
    private final MyNode prev;

    public MyNode(MyNode prevNode) {
        this.prev = prevNode;
    }
}
static {
    IPacket.writeObject(buffer, new MyNode(new MyNode(new MyNode)))
}
Composable объекты как и Serializable поддерживают тег transient для маркировки игнорируемых данных при сериализации.
private transient String игнорируемая_строка = "важная информация, не подверженная сериализации";
Composable объект должен иметь один и тот же classpath на обеих сторонах, чтобы не сломать сериализацию.
Экосистема Composible также включает в себя процесс ленивого обмена этими объектами между логическими сторонами. В качестве простого примера, ниже я приведу пример реализации решения задачи отправки каждой нажатой игроком кнопки на клавиатуре серверу.
Java:
@Mod(modid = MODID)
public class ComposableMod {

    static final String MODID = "catcherexample";

    private PacketHandlerClient packetHandlerClient;
    private PacketHandlerServer packetHandlerServer;

    @Mod.EventHandler
    public void event(FMLInitializationEvent event) {
        if(event.getSide().isClient()) {
            packetHandlerClient = new PacketHandlerClient(MODID);
            MinecraftForge.EVENT_BUS.register(this);
        } else {
            packetHandlerServer = new PacketHandlerServer(MODID);
            PacketAPI.getComposableCatcherBus().register(this, TestKeyComposable.class);
        }
    }

    @SubscribeEvent
    public void event(InputEvent.KeyInputEvent event) {
        // it happens on the client side
        packetHandlerClient.sendComposable(new TestKeyComposable(Keyboard.getEventKey(), Keyboard.getEventKeyState()));
    }

    @ComposableCatcher
    public void catcher(TestKeyComposable testKeyComposable, EntityPlayer entityPlayer) {
        // it happens on the server side
        Arrays.stream(Keyboard.class.getDeclaredFields())
                .filter(field -> Modifier.isPublic(field.getModifiers()))
                .filter(field -> Modifier.isStatic(field.getModifiers()))
                .filter(field -> field.getType() == int.class)
                .filter(field -> {
                    Object o = null;
                    try {
                        o = field.get(null);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                    return ((int) o) == testKeyComposable.getKeyCode();
                })
                .findFirst()
                .ifPresent(field -> System.out.println("The player " + entityPlayer.getName() + (testKeyComposable.isPressed() ? " " : " un") + "pressed the key " + field.getName().replace("KEY_", "")));
    }
}

Как видно, подобные задачи становятся намного проще и быстрее в реализации с использованием Composable.
По сути, так можно было бы передавать любое событие, отсылать обновления мира или регистра.
  • Like
Реакции: BestFoxy и TheLivan
Автор
Cornell
Скачивания
9
Просмотры
329
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок

Последние обновления

  1. 1.1.0: Composable

    добавлены Composable объекты добавлена ленивая отправка данных дополнена документация и...
  2. 1.0.1: 1.7.10 как и заказывали

    введена поддержка 1.7.10 пофикшены критические баги улучшение кода
Сверху