Найти в Дзене
ZDG

Игра Apple на Rust: Перечислимые структуры

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

Чтобы не было скучно, сразу покажу результат, который получился:

Это демонстрация того, как на сцену добавлены два графических объекта: RLE-спрайт и текст. Реализация базируется на предыдущих материалах:

Что я планировал изначально:

У каждого объекта типа GameObject есть свой атрибут renderer, который хранит собственно рендерер, который умеет отображать данный объект.

Чтобы в GameObject можно было поместить разные рендереры, нужно оформить их через трейт.

-2

Всего есть три разных рендерера: рисующий прямоугольники RendererRect, рисующий спрайты RendererSprite и рисующий текст RendererText.

(Код пишу чисто для примера, даже не проверяю)

-3

Все три рендерера имеют трейт Renderer, в котором определен один метод render(), и который должен иметь универсальную сигнатуру для всех трёх.

-4

Так как сигнатура метода должна быть универсальной, на вход рендереров подаётся просто объект GameObject.

Предполагалось, что каждый рендерер возьмёт из GameObject те атрибуты, которые ему нужны. Например, RendererText должен взять атрибут text, то есть строку для вывода, а RendererSprite должен взять атрибут sprite.

-5

Но другим рендерерам эти атрибуты взаимно не нужны. GameObject можно было бы поделить на подклассы: у одного есть свойство rect, у другого sprite, а у третьего text. Иначе в каждом GameObject пришлось бы хранить все три свойства. Да и почему бы нет, но нельзя создать GameObject, проинициализировав только нужное свойство. Остальные тоже нужно инициализировать, но если они не нужны, в них нельзя тупо записать null. Вместо этого свойство должно иметь тип Option<T>. Чувствуете, как на ровном месте начинается геморрой? Это приводит к тому, что и те свойства, которые нужны, тоже становятся типа Option<T>. А этот тип надо разворачивать через match ... И всё для того, чтобы просто прочитать цвет, допустим.

Тогда может сделать разные типы GameObject, и в каждом типе назначать свой конкретный рендерер, уже не привязываясь к трейту и к сигнатуре метода? Допустим:

-6

Да, но теперь эти разные GameObject фиг запишешь в вектор, в котором они должны содержаться. Потому что они разного типа, да. А наследования нет.

Вместо этого нам предлагается создать из них перечислимый тип, который выглядит примерно так

-7

Да, это перечислимый тип, который содержит целые структуры! Теперь все эти GameObject'ы становятся типа GMO, и мы можем положить их в вектор типа Vec<GMO>.

Что же дальше? А дальше нужно проходить по вектору, получать из него объекты и рендерить их. Типа:

-8

Ведь у любого GameObject есть renderer?

-9

Есть-то он есть, но у типа GMO его нет :) А именно его мы получаем из вектора. И добавить, скажем, общее свойство renderer в перечислимый тип GMO невозможно, можно добавить только в варианты.

А значит, чтобы воспользоваться конкретным renderer из конкретного варианта, снова понадобится match:

-10

Но нет, не всё так просто :) Мы сделали match, но объект o по-прежнему не имеет атрибута renderer. Его надо добыть непосредственно из match следующим образом:

-11

По сути код свёлся к банальному набору if, хоть и не явно: если тип объекта такой, то вызовем вот это. Если такой, то вот это. И т.д. Смысла хранить рендереры в самих GameObject остаётся очень мало (если только у каждого вида рендереров появятся еще вариации, которые можно объединить внутрисемейным трейтом).

И, в общем-то, на этой грустной ноте можно закончить. В Rust без match никуда. Но позитив заключается в том, что всё-таки удалось реализовать базовые вещи, и наконец можно будет завершить игру (в скором времени).

Код проекта находится здесь:

GitHub - nandakoryaaa/rust-apple: Rust adaptation of "Apple" game from BK-0010

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

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