powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Java [игнор отключен] [закрыт для гостей] / Задача по многопоточности
18 сообщений из 43, страница 2 из 2
Задача по многопоточности
    #38397046
greenpo1son
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
greenpo1soncdtyjvпропущено...
Если честно - я понятия не имею, что такое ExecutableQueue. Но это не мешает мне утверждать, что ваш код нужно выкинуть. Есть такое понятие - code smell. Это когда даже не вникая в то, что делает код, можно сказать, что он неверный, отталкиваясь от косвенных признаков. Так вот, ваш код очень сильно "смеллит". Я даже не хочу писать конкретные ошибки - их слишком много, и нет смысла их исправлять.

Давайте так - объясните вашу задачу. и я накидаю вам нормальное решение.
DOUBLE OMG. Нука хотябы одну ошибку в студию! Если вы даже не поняли что за класс перед вами , то что вы сможете объяснить ? Ссылаетесь не понятно на что.
Код этот работает так:

Например есть пул с 30 потоками. Приходит запрос ,в Селекторе читаем буффер и хандлим пакеты. Появились пакеты на отправку , добавляем их в эту очередь, при том это умная очередь которая способна распознать выполняются в ней пакеты или нет. То есть очередь не допустит что бы сразу 30 пакетов (запросов) не забрали все 30 потоков в пуле а достаточно что 1 поток обрабатывает всю очередь ... напишите мне альтернативу
Этот способ я использовал в Lineage2 когда разбирал сервак.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397051
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvMasterZivВходим в блок synchronized (events), events пуст, и мы начинаем ждать события на том же events.
Но оно никогда не наступит, потому что взводится оно только в dispatchEvent(Event e), в другом блоке synchronized (events) ,
в который нельзя будет зайти, поскольку монитор events уже заблокирован.Коллега, я вынужден констатировать, что вы не знаете, как работает wait/notify.
Когда вызывается wait(), то поток освобождает захваченный монитор , и помещается в wait set этого монитора. Поэтому в другой блок synchronized(events) можно будет зайти .

Да, действительно. Геслинг скрестил ежа с ужом...
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397058
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
greenpo1sonНука хотябы одну ошибку в студию!
Ошибка 1: Кривой дизайн. Коллекция имплементирует Runnable и хранит внутри себя Executor. Хотя должно быть с точностью наоборот: коллекция хранит внутри себя только элементы, не имплементирует Runnable, а какой-то Exeсutor вычитывает из нее.
Ошибка 2: Переменная _state нe final => не безопасная публикация.
Ошибка 3: isEmpty() и size() не обернуты в synchronized => могут возвращать совершенно некорректные результаты, вплоть до отрицательных величин.
Но самая главная претензия - это, конечно, дизайн. Он ужасен.

greenpo1sonКод этот работает так:
Например есть пул с 30 потоками. Приходит запрос ,в Селекторе читаем буффер и хандлим пакеты. Появились пакеты на отправку , добавляем их в эту очередь, при том это умная очередь которая способна распознать выполняются в ней пакеты или нет. То есть очередь не допустит что бы сразу 30 пакетов (запросов) не забрали все 30 потоков в пуле а достаточно что 1 поток обрабатывает всю очередь ... напишите мне альтернативуПока условие непонятно.
1) Сколько может быть таких очередей в рамках одного экзекьютора?
2) Что плохого в том, что они будут хэндлить пакеты в параллель? То есть, почему плохо, что 30 пакетов разойдутся по 30 потокам в пуле?

Пока что, до конца не понимая задачу, раз вам вы хотите исполнять в одном потоке, то вот решение:
Код: java
1.
ExecutorService executor = Executors.newFixedThreadPool(1);
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397060
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
MasterZivДа, действительно. Геслинг скрестил ежа с ужом...Не понял вашей мысли. Поведение wait/notify - это классическое условие в не менее классическом мониторе Хоара.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397067
greenpo1son
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
cdtyjv,
Ошибка 3: isEmpty() и size() не обернуты в synchronized => могут возвращать совершенно некорректные результаты, вплоть до отрицательных величин.
Это обрабатывается внутри кода.

2) Что плохого в том, что они будут хэндлить пакеты в параллель? То есть, почему плохо, что 30 пакетов разойдутся по 30 потокам в пуле?
Представте себе что пользователь например игрок стреляет в игре , отходит ещё что нибудь делает , приходит пакеты запросы которые мы должны обработать в ПРАВИЛЬНОМ порядке! Если я сделаю так как вы предложили то подтверждение прыжка например придет быстрее чем подтверждения попадания в цель , хотя игрок с начала выстрелил,а только потом прыгнул.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397069
greenpo1son
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Точно такая же картина будет и у остальных игроков, у одного он прыгает у другого стреляет у 3 вообще летает ))
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397070
greenpo1son
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
greenpo1soncdtyjv,
Ошибка 3: isEmpty() и size() не обернуты в synchronized => могут возвращать совершенно некорректные результаты, вплоть до отрицательных величин.
Это обрабатывается внутри кода.

2) Что плохого в том, что они будут хэндлить пакеты в параллель? То есть, почему плохо, что 30 пакетов разойдутся по 30 потокам в пуле?
Представте себе что пользователь например игрок стреляет в игре , отходит ещё что нибудь делает , приходит пакеты запросы которые мы должны обработать в ПРАВИЛЬНОМ порядке! Если я сделаю так как вы предложили то подтверждение прыжка например придет быстрее чем подтверждения попадания в цель , хотя игрок с начала выстрелил,а только потом прыгнул.
И да не забывайте что мы все это записываем в ByteBuffer и если 30 потоков одновременно начнут записывать в 1 буффер то что произойдет ?
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397072
greenpo1son
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
greenpo1son,
Хотя они конечно записываются только в Селекторе, пул только подготавливает пакеты к записи в буффер.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397086
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
greenpo1son ,
Ок, ситуация проясняется. То есть вам нужен ордеринг в рамках одного игрока. Тогда вопрос такой: у вас сколько этих очередей? По одной на каждого игрока, или нет?
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397092
greenpo1son
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
cdtyjv greenpo1son ,
Ок, ситуация проясняется. То есть вам нужен ордеринг в рамках одного игрока. Тогда вопрос такой: у вас сколько этих очередей? По одной на каждого игрока, или нет?
У каждого игрока 2 очереди
Одна Executable для принятых пакетов, пакет прочитали и сразу исполнили всю очередь.
Второй простой ArrayDeque для содержание пакетов для отправки и добавления интересов в селектор (естественно синхронизованная)
По сабжу я хотел предложить ТС просто немного переделать мою очередь под 1 поток и юзать его вместо листов.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397109
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
greenpo1son ,
Ок, понял вас. Тогда к вашему коду следующие претензии:
1) Зачем вы имплементируете Queue, когда бОльшую часть ее методов вам не нужна? В результате, в классе куча ненужного кода.
2) Представим, что вы купили себе новый сервак с 16 ядрами. И в приложении создали тред пул с 16 потоками, который будет обрабатывать игроков. Они начинают играть, ходят, чатятся, закупаются, и т.д. Вроде все круто. Потом наступает вечер войн гильдий. В игру входит 16 + 1 = 17 игроков. И в какой-то момент они начинают слать очень много команд на ваш сервер: деруться, применяют аптечки, кастуют что-то, и т.д. Но у вас всего 16 потоков в пуле. Каждый из этих потоков начинает обрабатывать одного игрока, и никак не может выйти из run(), так как команды прибывают быстрее, чем обрабатываются. Результат: 16 игроков воюют нормально, а 17й не может ничего сделать, так как ваш тред пул никак не дойдет до его очереди. Это называется starvation.
3) В продолжении п.2, если игроки шлют больше команд, чем вы можете обработать, то они скапливаются, и со временем у вас вылетает OOME (хотя, может быть эта защита стоит у вас где-то в другом месте кода).

Я не знаю всех условий и особенностей вашей задачи, но я бы двигался примерно в таком направлении:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
public class TaskHolder {
    /** Максимальное количество запросов в очереди, что бы избежать OOME. */
    private final int maxSize;

    /** Маппинг с идентификатора игрока на группу задач. */
    private final Map<Long, TaskGroup> taskGrps = new LinkedHashMap<>();

    /** Очередь с группами задач. */
    private final BlockingQueue<Runnable> taskGrpQueue = new LinkedBlockingDeque<>();

    /** Общее количество задач в очереди. */
    private int size;

    /**
     * Конструктор.
     *
     * @param maxSize Максимальное количество задач в очереди.
     */
    public TaskHolder(int maxSize) {
        this.maxSize = maxSize;
    }

    /**
     * Добавить задачу в очередь.
     *
     * @param playerId Идентификатор игрока.
     * @param task Задача.
     */
    public synchronized void addTask(Long playerId, Runnable task) throws InterruptedException {
        // Ждем, пока не появится место в очереди.
        while (size >= maxSize)
            wait();

        TaskGroup taskGrp = taskGrps.get(playerId);

        if (taskGrp == null) {
            // Группы, ассоциированной с таким игроком, еще нет.
            taskGrp = new TaskGroup(playerId);

            taskGrp.tasks.add(task);

            taskGrps.put(playerId, taskGrp);

            taskGrpQueue.add(taskGrp);
        }
        else
            // Группа, ассоциированная с таким игроком, есть.
            taskGrp.tasks.add(task);

        size++;
    }

    /**
     * Получить очередь сгруппированных задач.
     *
     * @return Очередь сгруппированных задач.
     */
    public BlockingQueue<Runnable> taskGroupQueue() {
        return taskGrpQueue;
    }

    /**
     * Группа задач для одного игрока.
     */
    private class TaskGroup implements Runnable {
        /** Идентификатор игрока. */
        private final Long playerId;

        /** Задачи в данной группе. */
        private final Deque<Runnable> tasks = new ConcurrentLinkedDeque<>();

        /**
         * Конструктор.
         *
         * @param playerId Идентификатор игрока.
         */
        private TaskGroup(Long playerId) {
            this.playerId = playerId;
        }

        /** {@inheritDoc} */
        @Override public void run() {
            int tasksSize = tasks.size();

            // Вычитываем столько тасков, сколько их было в момент вызова
            // Метода run(). Иначе мы рисуем столкнуться со starvation, когда
            // постоянно прибывающие новые таски от одного игрока подавляют ранее
            // пришедшие таски от другого игрока.
            for (int i = 0; i < tasksSize; i++) {
                Runnable task = tasks.poll();

                synchronized (Handler.this) {
                    size--;

                    notifyAll();
                }

                try {
                    task.run();
                }
                catch (Exception e) {
                    // Обработка эксепшна, пришедшего из таска.
                }
            }

            synchronized (Handler.this) {
                // Если новых задач для данной группы не поступало, удалить ее.
                // Иначе - поставить в конец очереди.
                if (tasks.isEmpty())
                    taskGrps.remove(playerId);
                else
                    taskGrpQueue.add(this);
            }
        }
    }
}


То есть у нас есть класс TaskHolder с очень простым и понятным интерфейсом: добавить таск для конкретного игрока, получить список задач. Ваш NIO хэндлер закидывает сюда задачи, а какой-нибудь другой воркер в бесконечном цикле читает из taskGroupQueue(), и закидывает группу тасков в какой-нибудь executor.
Все, никаких ненужных методов нет, очередь ничего не знает про executor и никак от него не зависит, есть защита от starvation, есть защита от OOME.
Гонок вроде нет, но может быть я что-то и упустил.

Автору ваш совет не подходит, так как ему запретили использовать j.u.c, плюс у вас совершенно разные задачи: вам надо создать очередь задач, а ему по сути надо сделать воркер, который будет обрабатывать задачи в бесконечном цикле.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397115
greenpo1son
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
cdtyjv greenpo1son ,
Ок, понял вас. Тогда к вашему коду следующие претензии:
1) Зачем вы имплементируете Queue, когда бОльшую часть ее методов вам не нужна? В результате, в классе куча ненужного кода.
2) Представим, что вы купили себе новый сервак с 16 ядрами. И в приложении создали тред пул с 16 потоками, который будет обрабатывать игроков. Они начинают играть, ходят, чатятся, закупаются, и т.д. Вроде все круто. Потом наступает вечер войн гильдий. В игру входит 16 + 1 = 17 игроков. И в какой-то момент они начинают слать очень много команд на ваш сервер: деруться, применяют аптечки, кастуют что-то, и т.д. Но у вас всего 16 потоков в пуле. Каждый из этих потоков начинает обрабатывать одного игрока, и никак не может выйти из run(), так как команды прибывают быстрее, чем обрабатываются. Результат: 16 игроков воюют нормально, а 17й не может ничего сделать, так как ваш тред пул никак не дойдет до его очереди. Это называется starvation.
3) В продолжении п.2, если игроки шлют больше команд, чем вы можете обработать, то они скапливаются, и со временем у вас вылетает OOME (хотя, может быть эта защита стоит у вас где-то в другом месте кода).

Я не знаю всех условий и особенностей вашей задачи, но я бы двигался примерно в таком направлении:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
public class TaskHolder {
    /** Максимальное количество запросов в очереди, что бы избежать OOME. */
    private final int maxSize;

    /** Маппинг с идентификатора игрока на группу задач. */
    private final Map<Long, TaskGroup> taskGrps = new LinkedHashMap<>();

    /** Очередь с группами задач. */
    private final BlockingQueue<Runnable> taskGrpQueue = new LinkedBlockingDeque<>();

    /** Общее количество задач в очереди. */
    private int size;

    /**
     * Конструктор.
     *
     * @param maxSize Максимальное количество задач в очереди.
     */
    public TaskHolder(int maxSize) {
        this.maxSize = maxSize;
    }

    /**
     * Добавить задачу в очередь.
     *
     * @param playerId Идентификатор игрока.
     * @param task Задача.
     */
    public synchronized void addTask(Long playerId, Runnable task) throws InterruptedException {
        // Ждем, пока не появится место в очереди.
        while (size >= maxSize)
            wait();

        TaskGroup taskGrp = taskGrps.get(playerId);

        if (taskGrp == null) {
            // Группы, ассоциированной с таким игроком, еще нет.
            taskGrp = new TaskGroup(playerId);

            taskGrp.tasks.add(task);

            taskGrps.put(playerId, taskGrp);

            taskGrpQueue.add(taskGrp);
        }
        else
            // Группа, ассоциированная с таким игроком, есть.
            taskGrp.tasks.add(task);

        size++;
    }

    /**
     * Получить очередь сгруппированных задач.
     *
     * @return Очередь сгруппированных задач.
     */
    public BlockingQueue<Runnable> taskGroupQueue() {
        return taskGrpQueue;
    }

    /**
     * Группа задач для одного игрока.
     */
    private class TaskGroup implements Runnable {
        /** Идентификатор игрока. */
        private final Long playerId;

        /** Задачи в данной группе. */
        private final Deque<Runnable> tasks = new ConcurrentLinkedDeque<>();

        /**
         * Конструктор.
         *
         * @param playerId Идентификатор игрока.
         */
        private TaskGroup(Long playerId) {
            this.playerId = playerId;
        }

        /** {@inheritDoc} */
        @Override public void run() {
            int tasksSize = tasks.size();

            // Вычитываем столько тасков, сколько их было в момент вызова
            // Метода run(). Иначе мы рисуем столкнуться со starvation, когда
            // постоянно прибывающие новые таски от одного игрока подавляют ранее
            // пришедшие таски от другого игрока.
            for (int i = 0; i < tasksSize; i++) {
                Runnable task = tasks.poll();

                synchronized (Handler.this) {
                    size--;

                    notifyAll();
                }

                try {
                    task.run();
                }
                catch (Exception e) {
                    // Обработка эксепшна, пришедшего из таска.
                }
            }

            synchronized (Handler.this) {
                // Если новых задач для данной группы не поступало, удалить ее.
                // Иначе - поставить в конец очереди.
                if (tasks.isEmpty())
                    taskGrps.remove(playerId);
                else
                    taskGrpQueue.add(this);
            }
        }
    }
}


То есть у нас есть класс TaskHolder с очень простым и понятным интерфейсом: добавить таск для конкретного игрока, получить список задач. Ваш NIO хэндлер закидывает сюда задачи, а какой-нибудь другой воркер в бесконечном цикле читает из taskGroupQueue(), и закидывает группу тасков в какой-нибудь executor.
Все, никаких ненужных методов нет, очередь ничего не знает про executor и никак от него не зависит, есть защита от starvation, есть защита от OOME.
Гонок вроде нет, но может быть я что-то и упустил.

Автору ваш совет не подходит, так как ему запретили использовать j.u.c, плюс у вас совершенно разные задачи: вам надо создать очередь задач, а ему по сути надо сделать воркер, который будет обрабатывать задачи в бесконечном цикле.

Зачем это все?
Во первых буффер не резиновый , а всего 32кб + кеш буфферов 64шт.
Во вторых есть определенный тайм оут который определяет время первого пакета на отправку после которого пишутся пакеты,
так же имеется ограничители на max кол-во пакетов и также размер запроса не может привышать 32кб (в 32кб может поместится около 500 пакетов) и если же начнется такой хаус (500 пакетов это уже не нормально, нормально считает 32 пакета за раз) то просто кикаем игрока.
Ваш метод мне кажется не подходит, так как не допустимо ждать пока освободится место в очереди.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397117
greenpo1son
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
1) Зачем вы имплементируете Queue, когда бОльшую часть ее методов вам не нужна? В результате, в классе куча ненужного кода.
Для совместимости , и работа на уровне суперкласса как бы поощряется.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397132
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
greenpo1son1) Зачем вы имплементируете Queue, когда бОльшую часть ее методов вам не нужна? В результате, в классе куча ненужного кода.
Для совместимости , и работа на уровне суперкласса как бы поощряется.Для совместимости с чем? И факт того, что вы имплементируете интерфейс коллекции таким образом, что:
1) Половина методов тупо не заимплементирована, включая iterator()!!!
2) Часть заимплементированных методов работают некорректно (size(), isEmpty(),
... не может поощряться.
Это какой-то класс мутант, который вроде бы и коллекция ... но как коллекцию его использовать нельзя.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397137
mikhail_zh
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
greenpo1son
Код: java
1.
2.
3.
4.
5.
6.
7.
 public void addListener(Listener l) {
        synchronized (listenersLock) {
            List<Listener> newListeners = new ArrayList<Listener>(listeners);
            newListeners.add(l);
            listeners = newListeners;
        }
    }


Вы постоянно присваиваете новый ArrayList?

Да, это самопальный CopyOnWrite . Т.к. слушатели редко изменяются и часто читаются, то думаю подход оправдан. На сколько мне известно этот метод для слушателей в Свинге используется.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397139
greenpo1son
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
cdtyjvgreenpo1son1) Зачем вы имплементируете Queue, когда бОльшую часть ее методов вам не нужна? В результате, в классе куча ненужного кода.
Для совместимости , и работа на уровне суперкласса как бы поощряется.Для совместимости с чем? И факт того, что вы имплементируете интерфейс коллекции таким образом, что:
1) Половина методов тупо не заимплементирована, включая iterator()!!!
2) Часть заимплементированных методов работают некорректно (size(), isEmpty(),
... не может поощряться.
Это какой-то класс мутант, который вроде бы и коллекция ... но как коллекцию его использовать нельзя.
Все верно сделано потому что это не коллекция , а рабочий queue к которому прямого доступа из когда нету. Ему не нужен итератор,
так все запросы обрабатываются в нем ,а не через него. То что я вам показал это всего 1% из всего кода.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397140
greenpo1son
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Для совместимости с чем? И факт того, что вы имплементируете интерфейс коллекции таким образом, что:
С тем что любой другой программист может дописать или как то улучшить и просто поменять конкретный объект, а не весь связанный с ним код.
...
Рейтинг: 0 / 0
Задача по многопоточности
    #38397173
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvMasterZivДа, действительно. Геслинг скрестил ежа с ужом...Не понял вашей мысли. Поведение wait/notify - это классическое условие в не менее классическом мониторе Хоара.

Зачем event сопрягать с семафором?

Я на самом деле был бы очень благодарен, если бы ты мне разъяснил... Сегодня весь день над этим думал.
...
Рейтинг: 0 / 0
18 сообщений из 43, страница 2 из 2
Форумы / Java [игнор отключен] [закрыт для гостей] / Задача по многопоточности
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]