Найти тему
ZDG

Игра Apple на Rust: Трусливый код и Event Loop

Предыдущие части: Первый результат, Лыко-мочало, Время жизни, Графическая прокладка, Дженерики, Композиция, Модули, Начальное проектирование, Итоги про память, Что там с памятью, Колхозим интерфейсы, Где у него классы, Поддержка SDL2, Полируем ржавчину

Чтобы не затягивать этот, а вместе с ним и другие проекты, решил сменить тактику.

Постараюсь быстро дописать игру Apple, но не буду акцентироваться на Rust более, чем необходимо.

Я напишу трусливый код. Это значит, что код будет работать и выглядеть как нормальный, но я сознательно буду избегать тех моментов, которые создают затруднения.

А непосредственно с Rust разберёмся как-нибудь потом, в отдельной сериии материалов, где можно будет погружаться сколь угодно глубоко.

MVC

Так же как и в игре "Питон на Питоне", для организации интерфейса игры буду применять шаблон MVC. Контроллер, представление и модель я просто адаптирую отсюда:

Но их код пока представлять не буду, так как уже сейчас я его два раза переписал и три раза сломал.

Лучше пока разобраться с циклом ожидания событий (Event Loop). Так как я использую библиотеку SDL2, то организация цикла будет такая же, как и в проекте про Питона, где используется библиотека pygame, которая в свою очередь использует SDL2.

Вот реализация на Питоне:

Что же получается на Rust?

-2

В варианте на Питоне полезные события с типом "клавиша нажата" накапливались в массиве controller_events, который затем передавался в контроллер в метод update().

Таким образом контроллер получает как бы уже отфильтрованные события.

В Rust я сделал слегка по-другому, добавив класс Input. Он тоже накапливает события, но также преобразует их из базовых типа "клавиша нажата" в конкретные типа "активно движение влево" или "активно движение вправо". Сооветственно в контроллер в метод update() передаётся сам объект Input, владеющий текущим логическим состоянием ввода. Контроллеру теперь не надо знать, какая клавиша отвечает за какое действие (и клавиша ли это вообще).

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

let evt_option = evt_pump.poll_event();

Это стандартное для Event Loop получение очередного события. Но получим мы не Event, а Option. Если вы уже забыли, то Option это перечислимый тип, который содержит либо None, либо Some(Event).

Следующие действия:

if evt_option != None {
let evt = evt_option.unwrap();

Если мы получили не None, то событие нужно достать из Some через unwrap() и записать в переменую evt.

Наконец, нужно проверить, что за тип у события evt. Нас интересуют только нажатия клавиш. Но как видно в том числе из кода Питона, мы можем получить событие с типом QUIT, когда пользователь закрывает программу. Поэтому его надо обработать сначала.

В Питоне всё происходит вполне прозрачно

if evt.type == pygame.QUIT...

Но в Rust мы видим какую-то ахинею:

-3

Видите ли, в данной адаптации SDL2 попросту нет структуры Event, у которой есть свойство type.

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

и т.д.
и т.д.

Таким образом, чтобы узнать, какой у события тип, мы должны делать match, который аналог switch в других языках, но посмотрите, как всё непросто:

Event::QUIT { .. }

Хорошо, мы нашли совпадение по перечислимому типу Event::QUIT, но точки в фигурных скобках { .. } говорят о том, что совпадение может дополнительно искаться по какому-то свойству внутри структуры.

В данном случае точки обозначают, что свойства надо просто игнорировать. Но можно было бы написать, например, так:

Event::QUIT { timestamp: 123 }

И вот я даже не знать не хочу, как конкретно внутри реализован этот match, так как очевидно, что он тяжёлый.

Что ж, дальше с помощью того же match можно узнать, не нажата ли клавиша:

-5

Наверно, сейчас можно уже понять, что это значит: найти соответствие с перечислимым типом Event::KeyDown, у которого свойство keycode имеет значение Some(k) – да, это снова Option, а другие свойства игнорируются.

Ну и тут меня опять же очень сильно нервирует факт, что мы пишем Some(k), тем самым вводя некую переменную k, которая является уже непосредственно кодом нажатой клавиши, но сам синтаксис Some ведь не используется так, какого чёрта. Короче, оно выглядит вроде как читабельно, но дико тревожно.

Наконец добравшись до кода нажатой клавиши, можно в Input активировать те или иные события: move_left(), move_right(), stop(). На деле логика обработки кодов клавиш должна находиться в самом Input, так как только Input знает, какими клавишами что надо активировать.

Но это пока не важно – ждут другие проблемы.

Читайте дальше: