[Решено] Multiple lines in tablist

  • Автор темы Автор темы CoolBoy
  • Дата начала Дата начала
Статус
Закрыто для дальнейших ответов.

CoolBoy

Активный участник
Пользователь
Баллы
96
Ник в Minecraft
Xezard
Всем доброго времени суток, уважаемые форумчане.

Во время написания плагина для заказчика столкнулся с некоторой проблемой.

Так как у SpigotAPI ещё нет метода взаимодействия с "таб-листом" приходится немного костылить.

После прочтения некоторой документации и декомпиляции парочки плагинов на "таб-лист", я таки написал свой плагин.

Но возникла небольшая проблема.

Дело в том, что и Header и Footer плагин успешно устанавливает игрокам. Но как только я пытаюсь выставить в "таб-лист" несколько строк - он выводит только первые.

Вопрос:
Каким образом можно выставить в "таб-лист" несколько строк с текстом?
И в footer, и header.

Код методов, которые я использую:
Utils.class
Java:
public static void sendTablist(Player player, String string, String string_2, Main plugin)
    {
        try
        {
            if (Utils.getServerVersion().equalsIgnoreCase("v1_9_R1") || Utils.getServerVersion().equalsIgnoreCase("v1_9_R2"))
            {
                Object header = Utils.getNmsClass("ChatComponentText").getConstructor(String.class).newInstance(string);
                Object footer = Utils.getNmsClass("ChatComponentText").getConstructor(String.class).newInstance(string_2);
                Object util = Utils.getNmsClass("PacketPlayOutPlayerListHeaderFooter").getConstructor(Utils.getNmsClass("IChatBaseComponent")).newInstance(header);
                Field field = util.getClass().getDeclaredField("b");
                field.setAccessible(true);
                field.set(util, footer);
                Object nmsp = player.getClass().getMethod("getHandle", new Class[0]).invoke(player, new Object[0]);
                Object pcon = nmsp.getClass().getField("playerConnection").get(nmsp);
                pcon.getClass().getMethod("sendPacket", Utils.getNmsClass("Packet")).invoke(pcon, util);
             }
        } catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException |
InstantiationException | NoSuchFieldException | NoSuchMethodException |
SecurityException | InvocationTargetException exception)
        {
            exception.printStackTrace();
        }
    }
}

private static String getServerVersion()
{
    return Bukkit.getServer().getClass().getPackage().getName().substring(23);
}

private static Class<?> getNmsClass(String string) throws ClassNotFoundException
{
    return Class.forName("net.minecraft.server." + Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3] + "." + string);
}
Listeners.class
Java:
@EventHandler
public void onJoin(PlayerJoinEvent event)
{
    for (Player players : Bukkit.getOnlinePlayers())
    {
        Utils.sendTablist(players, Utils.colorMessage(header), Utils.colorMessage(footer), this.plugin);
    }
}

Что я делал:
Создавал HashMap, брал из конфига лист строк, с помощью цикла помещал в хэшмап. Также циклом проходился по хешмапу и выставлял всем игрокам tablist. Но получалось так, что, зайдя на сервер первый раз, у меня и в Header, и в Footer были первые строки. Зайдя во второй - вторые. Каким образом я могу выставить их обе сразу?

P.S на данный момент, в качестве костыля, просто беру строку, в которой для переноса строк используется \n.
 
Последнее редактирование:
В данном случае - это костыль.
Код цикла и код StringJoiner выполняет одно и тоже, а значит - не костыль.

Зачем костылять, если есть уже готовое в Java SE? Пиши тогда еще и свою коллекцию.
Я такого нигде не говорил.
Зачем костылять, если есть уже готовое в Java SE?
Сказали. И если программер не умеет делать реализации простых структур данных - такие никому не нужны.
 
Код цикла и код StringJoiner выполняет одно и тоже, а значит - не костыль.




Сказали. И если программер не умеет делать реализации простых структур данных - такие никому не нужны.
"Я лучше знаю, что ты сказал."
Костыль. И посмотри код StringJoiner'а, там принцип другой, да и производительность выше.
И если программер не знает Java SE API, такой тоже никому не нужен. От него можно ждать только костыльные реализации.
 
Последнее редактирование:
"Я лучше знаю, что ты сказал."
Костыль. И посмотри код StringJoiner'а, там принцип другой, да и производительность выше.
И если программер не знает Java SE API, такой тоже никому не нужен. От него можно ждать только костыльные реализации.
Костыль - это кривая, но работающая затычка в коде. В данном случае собственная реализация стрингджойнера - велосипед, но может человек не может в жабу 8, всякое говно бывает.
 
Костыль - это кривая, но работающая затычка в коде. В данном случае собственная реализация стрингджойнера - велосипед, но может человек не может в жабу 8, всякое говно бывает.
Было бы велосипедом, если бы использовал хотя бы StringBuilder, а так - костыль.
Если элементов много будет, то сколько там объектов StringBuilder наплодится?
 
Код цикла и код StringJoiner выполняет одно и тоже, а значит - не костыль.




Сказали. И если программер не умеет делать реализации простых структур данных - такие никому не нужны.
Ваш велосипед - говно, что по производительности, что по стилю кода.

А теперь к теме тса, просто \n - символ переноса, для тех кто не знает, потому и логично что он переносит строки, правда не везде в майнкрафте есть его поддержка. Можно ещё посмотреть сколько максимум символов вмещает строка и добивать пробелами, но вот это уже изврат.
 
С
Java:
 if (i == WORD_COUNT - 1)
заорал.
Но если что, твой примитив иногда действительно быстрее.
Примитив будет быстрее только в случае 1го элемента, потому что у стрингджойнера есть накладные расходы на инициализацию стрингбилдера. Случаи до 100 элементов надо бенчить с учётом прогрева jvm, но это уже тонкости. В общем случае лучше использовать StringBuilder при конкатенации большого либо неизвестного заранее количества символов (строк).
 
Примитив будет быстрее только в случае 1го элемента
Оказывается, что нет. Он так же быстрее, если элементов больше 850 тысяч.
Java:
/**
* Little edit for much perfomance.
* @author fall.us
*/
public class Main {

    final static int WORD_COUNT = 1000000;

    public static void main(String[] args) {
        Random rand = new Random(System.currentTimeMillis());
        String[] strings = new String[WORD_COUNT];

        for (int i = 0; i < WORD_COUNT; i++) {
            strings[i] = String.valueOf(rand.nextLong());
        }
    
        long clockStrings, clockSJoiner, clockStart = System.currentTimeMillis();
    
        StringBuilder words = new StringBuilder();
        for (int i = 0; i < WORD_COUNT; i++) {
            words.append(strings[i]).append("\n");
        }

        clockStrings = System.currentTimeMillis() - clockStart;
        clockStart = System.currentTimeMillis();

        StringJoiner sj = new StringJoiner("\n");
        for (int i = 0; i < WORD_COUNT; i++) {
            sj.add(strings[i]);
        }

        clockSJoiner = System.currentTimeMillis() - clockStart;
        words = new StringBuilder();
        words.append("Overall time: ").append((clockSJoiner + clockStrings)).append(" ms\n");
        words.append("Strings: ").append(clockStrings).append(" ms\n");
        words.append("StringJoiner: ").append(clockSJoiner).append(" ms");
        System.out.println(words.toString());
    }
}
 
Оказывается, что нет. Он так же быстрее, если элементов больше 850 тысяч.
Java:
/**
* Little edit for much perfomance.
* @author fall.us
*/
public class Main {

    final static int WORD_COUNT = 1000000;

    public static void main(String[] args) {
        Random rand = new Random(System.currentTimeMillis());
        String[] strings = new String[WORD_COUNT];

        for (int i = 0; i < WORD_COUNT; i++) {
            strings[i] = String.valueOf(rand.nextLong());
        }
   
        long clockStrings, clockSJoiner, clockStart = System.currentTimeMillis();
   
        StringBuilder words = new StringBuilder();
        for (int i = 0; i < WORD_COUNT; i++) {
            words.append(strings[i]).append("\n");
        }

        clockStrings = System.currentTimeMillis() - clockStart;
        clockStart = System.currentTimeMillis();

        StringJoiner sj = new StringJoiner("\n");
        for (int i = 0; i < WORD_COUNT; i++) {
            sj.add(strings[i]);
        }

        clockSJoiner = System.currentTimeMillis() - clockStart;
        words = new StringBuilder();
        words.append("Overall time: ").append((clockSJoiner + clockStrings)).append(" ms\n");
        words.append("Strings: ").append(clockStrings).append(" ms\n");
        words.append("StringJoiner: ").append(clockSJoiner).append(" ms");
        System.out.println(words.toString());
    }
}
В другое русло пошли, господа.
 
Оказывается, что нет. Он так же быстрее, если элементов больше 850 тысяч.
Java:
/**
* Little edit for much perfomance.
* @author fall.us
*/
public class Main {

    final static int WORD_COUNT = 1000000;

    public static void main(String[] args) {
        Random rand = new Random(System.currentTimeMillis());
        String[] strings = new String[WORD_COUNT];

        for (int i = 0; i < WORD_COUNT; i++) {
            strings[i] = String.valueOf(rand.nextLong());
        }
   
        long clockStrings, clockSJoiner, clockStart = System.currentTimeMillis();
   
        StringBuilder words = new StringBuilder();
        for (int i = 0; i < WORD_COUNT; i++) {
            words.append(strings[i]).append("\n");
        }

        clockStrings = System.currentTimeMillis() - clockStart;
        clockStart = System.currentTimeMillis();

        StringJoiner sj = new StringJoiner("\n");
        for (int i = 0; i < WORD_COUNT; i++) {
            sj.add(strings[i]);
        }

        clockSJoiner = System.currentTimeMillis() - clockStart;
        words = new StringBuilder();
        words.append("Overall time: ").append((clockSJoiner + clockStrings)).append(" ms\n");
        words.append("Strings: ").append(clockStrings).append(" ms\n");
        words.append("StringJoiner: ").append(clockSJoiner).append(" ms");
        System.out.println(words.toString());
    }
}
Скорее всего аллокация памяти для стрингбилдера вызвала аллокацию памяти из системы, запускай из консоли с достаточным количеством выделенной памяти, ибо у меня на любом количестве элементов которые ещё могут влезть в массив джойнер быстрее.
 
Скорее всего аллокация памяти для стрингбилдера вызвала аллокацию памяти из системы, запускай из консоли с достаточным количеством выделенной памяти, ибо у меня на любом количестве элементов которые ещё могут влезть в массив джойнер быстрее.
Вообще интересно выходит. 349 мб рамы отъедает.
2016 06 15_14-41-08.png

Интересная картина. Но всё равно StringJoiner для плагинов будет быстрее, ибо на сервере майнкрафта не найдётся столько объектов, чтобы замедлить его (кроме блоков - они замедляют всё что можно и нельзя).
В другое русло пошли, господа.
Да плевать.
 
Вообще интересно выходит. 349 мб рамы отъедает.

Интересная картина. Но всё равно StringJoiner для плагинов будет быстрее, ибо на сервере майнкрафта не найдётся столько объектов, чтобы замедлить его (кроме блоков - они замедляют всё что можно и нельзя).
Да плевать.
Xms забыл чтобы вся память сразу выделилась.
 
Последнее редактирование:
СтрингБилдер почти всегда будет быстрее, так как конкатенация объектов String вызывает новый объект StringBuilder'а.
Хотел уже написать чзх, но потом увидел у себя очепятку. Спасибки.
 
Вот нормальный тест:
Java:
package dk.xakeps.benchmarks.strings;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

@State(Scope.Thread)
@SuppressWarnings({"all"})
public class Main {

    private StringBuilder stringBuilder1;
    private StringBuilder stringBuilder2;

    private StringBuffer stringBuffer1;
    private StringBuffer stringBuffer2;

    private String string1;
    private String string2;

    /*
     * re-initializing the value after every iteration
     */
    @Setup(Level.Iteration)
    public void init() {
        stringBuilder1 = new StringBuilder("foo");
        stringBuilder2 = new StringBuilder("bar");

        stringBuffer1 = new StringBuffer("foo");
        stringBuffer2 = new StringBuffer("bar");

        string1 = "foo";
        string2 = "bar";

    }

    @State(Scope.Benchmark)
    public static class BenchmarkState {
        volatile long builderConcatCount = 1;
        volatile long bufferConcatCount = 1;
        volatile long stringPlusCount = 1;
        volatile long stringConcatCount = 1;
    }

    /*
     * want to measure the progress from call to call so using the
     * SingleShotTime mode
     */
    @Benchmark
    @Warmup(iterations = 5, batchSize = 1)
    @Measurement(iterations = 100, batchSize = 1000)
    @BenchmarkMode(Mode.SingleShotTime)
    public StringBuilder stringBuilder(BenchmarkState state) {
        StringBuilder stringBuilder = null;
        for (long j = 0; j < state.builderConcatCount; j++) {
            stringBuilder = stringBuilder1.append(stringBuilder2);
        }
        state.builderConcatCount++;
        // to avoid dead code optimization returning the value
        return stringBuilder;
    }

    @Benchmark
    @Warmup(iterations = 5, batchSize = 1)
    @Measurement(iterations = 100, batchSize = 1000)
    @BenchmarkMode(Mode.SingleShotTime)
    public StringBuffer stringBuffer(BenchmarkState state) {
        StringBuffer stringBuffer = null;
        for (long j = 0; j < state.bufferConcatCount; j++) {
            stringBuffer = stringBuffer1.append(stringBuffer2);
        }
        state.bufferConcatCount++;
        // to avoid dead code optimization returning the value
        return stringBuffer;
    }

    /*
     * want to measure the progress from call to call so using the
     * SingleShotTime mode
     */
    @Benchmark
    @Warmup(iterations = 5, batchSize = 1)
    @Measurement(iterations = 100, batchSize = 1000)
    @BenchmarkMode(Mode.SingleShotTime)
    public String stringPlus(BenchmarkState state) {
        String string = null;
        for (long j = 0; j < state.stringPlusCount; j++) {
            string = string1 + string2;
        }
        state.stringPlusCount++;
        // to avoid dead code optimization returning the value
        return string;
    }

    @Benchmark
    @Warmup(iterations = 5, batchSize = 1)
    @Measurement(iterations = 100, batchSize = 1000)
    @BenchmarkMode(Mode.SingleShotTime)
    public String stringConcat(BenchmarkState state) {
        String string = null;
        for (long j = 0; j < state.stringConcatCount; j++) {
            string = string1.concat(string2);
        }
        state.stringConcatCount++;
        // to avoid dead code optimization returning the value
        return string;
    }


    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(Main.class.getSimpleName())
                .threads(1)
                .forks(1)
                .shouldFailOnError(true)
                .shouldDoGC(true)
                .jvmArgs("-server", "-Xmx6G", "-Xms6G", "-XX:+UseG1GC")
                .build();
        new Runner(options).run();
    }
}
Результаты:
Код:
"-server", "-Xmx6G", "-Xms2G", "-XX:+UseConcMarkSweepGC"
# Run complete. Total time: 00:06:50

Benchmark           Mode  Cnt  Score   Error  Units
Main.stringBuffer     ss  100  0,733 ± 0,142   s/op
Main.stringBuilder    ss  100  0,508 ± 0,107   s/op
Main.stringConcat     ss  100  1,160 ± 0,226   s/op
Main.stringPlus       ss  100  0,602 ± 0,117   s/op

"-server", "-Xmx6G", "-Xms6G", "-XX:+UseConcMarkSweepGC"
# Run complete. Total time: 00:06:55

Benchmark           Mode  Cnt  Score   Error  Units
Main.stringBuffer     ss  100  0,679 ± 0,123   s/op
Main.stringBuilder    ss  100  0,482 ± 0,094   s/op
Main.stringConcat     ss  100  1,221 ± 0,243   s/op
Main.stringPlus       ss  100  0,661 ± 0,132   s/op

"-server", "-Xmx6G", "-Xms2G", "-XX:+UseG1GC"
# Run complete. Total time: 00:07:58

Benchmark           Mode  Cnt  Score   Error  Units
Main.stringBuffer     ss  100  0,717 ± 0,125   s/op
Main.stringBuilder    ss  100  0,489 ± 0,091   s/op
Main.stringConcat     ss  100  1,388 ± 0,278   s/op
Main.stringPlus       ss  100  0,750 ± 0,152   s/op

"-server", "-Xmx6G", "-Xms6G", "-XX:+UseG1GC"
# Run complete. Total time: 00:08:11

Benchmark           Mode  Cnt  Score   Error  Units
Main.stringBuffer     ss  100  0,752 ± 0,149   s/op
Main.stringBuilder    ss  100  0,516 ± 0,091   s/op
Main.stringConcat     ss  100  1,300 ± 0,261   s/op
Main.stringPlus       ss  100  0,625 ± 0,118   s/op
Вывод: скорость зависит от настроек GC, а не от Xms.
 
Вот нормальный тест:
Java:
package dk.xakeps.benchmarks.strings;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

@State(Scope.Thread)
@SuppressWarnings({"all"})
public class Main {

    private StringBuilder stringBuilder1;
    private StringBuilder stringBuilder2;

    private StringBuffer stringBuffer1;
    private StringBuffer stringBuffer2;

    private String string1;
    private String string2;

    /*
     * re-initializing the value after every iteration
     */
    @Setup(Level.Iteration)
    public void init() {
        stringBuilder1 = new StringBuilder("foo");
        stringBuilder2 = new StringBuilder("bar");

        stringBuffer1 = new StringBuffer("foo");
        stringBuffer2 = new StringBuffer("bar");

        string1 = "foo";
        string2 = "bar";

    }

    @State(Scope.Benchmark)
    public static class BenchmarkState {
        volatile long builderConcatCount = 1;
        volatile long bufferConcatCount = 1;
        volatile long stringPlusCount = 1;
        volatile long stringConcatCount = 1;
    }

    /*
     * want to measure the progress from call to call so using the
     * SingleShotTime mode
     */
    @Benchmark
    @Warmup(iterations = 5, batchSize = 1)
    @Measurement(iterations = 100, batchSize = 1000)
    @BenchmarkMode(Mode.SingleShotTime)
    public StringBuilder stringBuilder(BenchmarkState state) {
        StringBuilder stringBuilder = null;
        for (long j = 0; j < state.builderConcatCount; j++) {
            stringBuilder = stringBuilder1.append(stringBuilder2);
        }
        state.builderConcatCount++;
        // to avoid dead code optimization returning the value
        return stringBuilder;
    }

    @Benchmark
    @Warmup(iterations = 5, batchSize = 1)
    @Measurement(iterations = 100, batchSize = 1000)
    @BenchmarkMode(Mode.SingleShotTime)
    public StringBuffer stringBuffer(BenchmarkState state) {
        StringBuffer stringBuffer = null;
        for (long j = 0; j < state.bufferConcatCount; j++) {
            stringBuffer = stringBuffer1.append(stringBuffer2);
        }
        state.bufferConcatCount++;
        // to avoid dead code optimization returning the value
        return stringBuffer;
    }

    /*
     * want to measure the progress from call to call so using the
     * SingleShotTime mode
     */
    @Benchmark
    @Warmup(iterations = 5, batchSize = 1)
    @Measurement(iterations = 100, batchSize = 1000)
    @BenchmarkMode(Mode.SingleShotTime)
    public String stringPlus(BenchmarkState state) {
        String string = null;
        for (long j = 0; j < state.stringPlusCount; j++) {
            string = string1 + string2;
        }
        state.stringPlusCount++;
        // to avoid dead code optimization returning the value
        return string;
    }

    @Benchmark
    @Warmup(iterations = 5, batchSize = 1)
    @Measurement(iterations = 100, batchSize = 1000)
    @BenchmarkMode(Mode.SingleShotTime)
    public String stringConcat(BenchmarkState state) {
        String string = null;
        for (long j = 0; j < state.stringConcatCount; j++) {
            string = string1.concat(string2);
        }
        state.stringConcatCount++;
        // to avoid dead code optimization returning the value
        return string;
    }


    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(Main.class.getSimpleName())
                .threads(1)
                .forks(1)
                .shouldFailOnError(true)
                .shouldDoGC(true)
                .jvmArgs("-server", "-Xmx6G", "-Xms6G", "-XX:+UseG1GC")
                .build();
        new Runner(options).run();
    }
}
Результаты:
Код:
"-server", "-Xmx6G", "-Xms2G", "-XX:+UseConcMarkSweepGC"
# Run complete. Total time: 00:06:50

Benchmark           Mode  Cnt  Score   Error  Units
Main.stringBuffer     ss  100  0,733 ± 0,142   s/op
Main.stringBuilder    ss  100  0,508 ± 0,107   s/op
Main.stringConcat     ss  100  1,160 ± 0,226   s/op
Main.stringPlus       ss  100  0,602 ± 0,117   s/op

"-server", "-Xmx6G", "-Xms6G", "-XX:+UseConcMarkSweepGC"
# Run complete. Total time: 00:06:55

Benchmark           Mode  Cnt  Score   Error  Units
Main.stringBuffer     ss  100  0,679 ± 0,123   s/op
Main.stringBuilder    ss  100  0,482 ± 0,094   s/op
Main.stringConcat     ss  100  1,221 ± 0,243   s/op
Main.stringPlus       ss  100  0,661 ± 0,132   s/op

"-server", "-Xmx6G", "-Xms2G", "-XX:+UseG1GC"
# Run complete. Total time: 00:07:58

Benchmark           Mode  Cnt  Score   Error  Units
Main.stringBuffer     ss  100  0,717 ± 0,125   s/op
Main.stringBuilder    ss  100  0,489 ± 0,091   s/op
Main.stringConcat     ss  100  1,388 ± 0,278   s/op
Main.stringPlus       ss  100  0,750 ± 0,152   s/op

"-server", "-Xmx6G", "-Xms6G", "-XX:+UseG1GC"
# Run complete. Total time: 00:08:11

Benchmark           Mode  Cnt  Score   Error  Units
Main.stringBuffer     ss  100  0,752 ± 0,149   s/op
Main.stringBuilder    ss  100  0,516 ± 0,091   s/op
Main.stringConcat     ss  100  1,300 ± 0,261   s/op
Main.stringPlus       ss  100  0,625 ± 0,118   s/op
Вывод: скорость зависит от настроек GC, а не от Xms.
2гб - максимальный размер строки, аллокации лишней системной памяти так и так не произойдёт, xms надо вообще уносить, тогда совсем небольшая разница будет на малых количествах итераций. И прогрев надо тогда отрубать.
Кстати судя по результатам теста jvm в итоге плюс таки превратила в стрингбилдер, дампани выхлоп jit, да и байткод не помешал бы.
 
2гб - максимальный размер строки, аллокации лишней системной памяти так и так не произойдёт, xms надо вообще уносить, тогда совсем небольшая разница будет на малых количествах итераций. И прогрев надо тогда отрубать.
Кстати судя по результатам теста jvm в итоге плюс таки превратила в стрингбилдер, дампани выхлоп jit, да и байткод не помешал бы.
Ага, что-то не подумал об этом. Потом сделаю тесты заново.
 
А не, сам тестовый код косячный, ты скадываешь две строки каждый раз, в итоге тест тестит только скорость операций, нам же надо складывать всё в одну большую строку чтобы увидеть влияние сайд эффектов.
 
Статус
Закрыто для дальнейших ответов.
Назад
Верх Низ