doom2d.org

Главная база плоских морпехов
It is currently 30 Mar 2023, 20:49

All times are UTC + 3 hours




Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: 17 Mar 2023, 08:39 
Offline
Абсолютно неубиваем
User avatar

Joined: 18 Oct 2009, 04:01
Posts: 6332
Location: Владивосток
Вчитываясь в код ветки renders_updated, начал ловить себя на мысли, что его как-то сильно перекосило в сторону процедурности. Сам по себе DeaDDooMER однозначный молодец, спору нет, но он завзятый оберонист, и я вижу в происходящем некоторое влияние Оберона. Однако Object Pascal - это всё-таки объектный язык, а не процедурный, и я бы предпочёл использовать его по назначению. В противном случае это будут танцы с гирей на ноге: можно, конечно, и на C++ писать как будто на Lisp'е, или на "Си с классами", но зачем?

Что мне не нравится прям очень сильно в модульном подходе по сравнению с объектным, относительно Object Pascal:

  • постоянный name clashing между модулями (у меня вообще подозрение, что здесь он был сделан специально для поощрения использования объектов);
  • неясность связей между кодом в модулях (потому что общий namespace, опять же);
  • потребность выбирать между размытостью предназначения модулей и их количеством;
  • ориентированность на global state (общее состояние), провоцирующая тем самым его возникновение и разбухание;
  • необходимость перешвыривать разные context structure между теми или иными частями кода, что сводит на нет пользу избегания объектности;
  • смешивание модульного и объектного подходов приводит к вытаскиванию на свет божий тех вещей, которые вообще-то должны были быть private.

Справедливости ради, приведу здесь и список однозначных недостатков использования объектного подхода в Object Pascal:

  • он не является полноценной и честной реализацией ООП как такового (но, положа руку на сердце: а кто им является-то, кроме Smalltalk?);
  • хоть и несущественные, но лишние накладные расходы на поддержку этого подхода со стороны компилятора, вроде VMT (таблиц виртуальных методов) и скачков по указателям туда-сюда;
  • местами довольно-таки неуклюжий синтаксис;
  • сложность продумывания структуры кода (все вот эти вот "паттерны ООП" и прочий так называемый рефакторинг);
  • сложность построения правильных объектных иерархий без постоянного дублирования состояния между ними.

Непосредственно в renders_updated пока заметил такие примеры. Во-первых, раскрытие внутреннего состояния:

https://repo.or.cz/d2df-sdl.git/blob/626d356cbf900e44cc40fc3ce016e1fad6aff6f5:/src/game/g_playermodel.pas#l145
https://repo.or.cz/d2df-sdl.git/blob/626d356cbf900e44cc40fc3ce016e1fad6aff6f5:/src/game/g_player.pas#l368
https://repo.or.cz/d2df-sdl.git/blob/626d356cbf900e44cc40fc3ce016e1fad6aff6f5:/src/game/g_player.pas#l410

Во-вторых, код отрисовки сущностей (тех же игроков) уехал из самих сущностей в рендерер. Это здорово, если учесть, как оно было раньше (до этого отрисовка шла едва ли не прямыми вызовами к OpenGL из сущностей, буквально). Однако за это пришлось заплатить прямым нарушением объектности (сущности расползлись по коду шире своих объектов). Как мне видится, лучше всегда было бы оставить методы .Draw() у сущностей, но чтобы вызывал их сам рендерер, передавая туда ссылку на самого себя. Тогда сущности могли бы клацать нужные им ручки у рендерера для отрисовки.

Почему я поднял эту тему? Потому что в идеале код должен позволять нам делать "комнаты", то есть несколько игровых серверов в рамках одного процесса. Мне такая возможность кажется жизненно важной, учитывая нехватку доступных хостов и далеко не полную загрузку уже имеющихся. Но перепилить нынешнюю кодовую базу для поддержки этого, сохранив процедурный подход, для меня представляется едва ли возможным. Куда более практичным и реализуемым вариантом на данный момент видится первичное распиливание всего на объекты, которые не зависели бы от общего состояния и благодаря этому позволяли бы создавать себя по нескольку раз.

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

_________________
Чёрный Думер, Чёрный Думер
С монстрами сражается.
Чёрный Думер, Чёрный Думер
Рокетланчер плавится.


Top
 Profile  
 
PostPosted: 19 Mar 2023, 07:39 
Offline
User avatar

Joined: 25 Feb 2013, 13:24
Posts: 643
я ебусь в глаза и не вижу кнопки «подписаться». поэтому этот пост ни про что, чтобы подписало. простите.


Top
 Profile  
 
PostPosted: 19 Mar 2023, 16:45 
Offline
User avatar

Joined: 04 Feb 2010, 14:42
Posts: 632
Location: Equestria
Черный Думер wrote:
сильно перекосило в сторону процедурности
Не перекосило. Весь код и так процедурный, классы повсюду используются только как динамические record'ы. Делать иначе у меня не было оснований (соотвествующих структур данных). Цели переделать вообще весь код у меня не было, зато была цель что бы всё работало уже сейчас.
Черный Думер wrote:
Как мне видится, лучше всегда было бы оставить методы .Draw() у сущностей, но чтобы вызывал их сам рендерер, передавая туда ссылку на самого себя.
Это плохой подход. Игровая логика и игровые структуры данных ничего не должны знать о графике. Правильный - сделать дефолтовые рисовалки, которые может использовать рендер (а может и не использовать) и будет импортировать их только когда надо.
Черный Думер wrote:
в идеале код должен позволять нам делать "комнаты"
Да. Но в первую очередь надо сделать выдиралку игрового стейта в структуру, которая будет делать это где-то сбоку. Когда будет такая структура, тогда будут основания делать и более объектно-ориентированный код, и понимание куда ориентровать эти самые объекты. Делать объекты без оснований только усложнит код без практической пользы.


Top
 Profile  
 
PostPosted: 23 Mar 2023, 17:28 
Offline
Абсолютно неубиваем
User avatar

Joined: 18 Oct 2009, 04:01
Posts: 6332
Location: Владивосток
DeaDDooMER» Не перекосило. Весь код и так процедурный, классы повсюду используются только как динамические record'ы.
Ну, да. Я некорректно выразился. Имел в виду, что в коде появился ещё один чисто процедурный слой абстракции.

DeaDDooMER» Делать иначе у меня не было оснований (соотвествующих структур данных). Цели переделать вообще весь код у меня не было, зато была цель что бы всё работало уже сейчас.
Это не было камнем в твой огород ни в коем случае, если что. Просто общее замечание по нынешнему коду как таковому, с задумчиво-озадаченным прищурившимся взглядом.

DeaDDooMER» Это плохой подход. Игровая логика и игровые структуры данных ничего не должны знать о графике.
Здесь хотелось бы поспорить, но сначала хочу убедиться, что я правильно тебя понял.

DeaDDooMER» Правильный - сделать дефолтовые рисовалки, которые может использовать рендер (а может и не использовать) и будет импортировать их только когда надо.
Вот тут нужно пояснение, потому что я не смог уловить идею.

DeaDDooMER» Но в первую очередь надо сделать выдиралку игрового стейта в структуру, которая будет делать это где-то сбоку.
DeaDDooMER» Когда будет такая структура, тогда будут основания делать и более объектно-ориентированный код, и понимание куда ориентровать эти самые объекты.
DeaDDooMER» Делать объекты без оснований только усложнит код без практической пользы.
Разумеется.

_________________
Чёрный Думер, Чёрный Думер
С монстрами сражается.
Чёрный Думер, Чёрный Думер
Рокетланчер плавится.


Top
 Profile  
 
PostPosted: 23 Mar 2023, 19:05 
Offline
User avatar

Joined: 04 Feb 2010, 14:42
Posts: 632
Location: Equestria
Черный Думер wrote:
Здесь хотелось бы поспорить, но сначала хочу убедиться, что я правильно тебя понял.
1) Оставляя Draw мы либо тянем на сервере часть графического стека, либо повсюду ifdef'им эти Draw
2) Мы прибиваемся к порядку рендеринга (back-to-front, вместо front-to-back)
3) Мы прибиваемся к методу рендеринга (условные glBegin/glEnd, вместо VBO)
4) Для получения продвинутых эффектов выдумываем всякие сложные абстракции или их отсутствие (прямые вызовы опенгл)

Черный Думер wrote:
Вот тут нужно пояснение, потому что я не смог уловить идею.
Есть такой паттерн Model-View-Controller. Возьмем монстров и применим к ним такой паттерн. Получится:
Model = класс TMonster, чисто данные/стейт и методы манипуляции на ними.
View = этот рисовалка монстров (метод Draw, на сервере не нужен совсем).
Controller = думалка монстра (метод Update).
Вот я считаю что View-компонента должна быть строго отделена. А в дополнение можно сделать стандартную реализацию View (по твоему образу, но не прибито к TMonster, а отдельным классом или процедурой). Её сможет использовать рендер, если ему это ок. А может и рисовать всё сам, если он может разом нарисовать 100 монстров одним вызовом opengl, вместо ста дёрганий TMonster.Draw рисующей по одной текстурке. Всё тоже самое применимо и к другим объектам.

Не важно что скорее всего будет рисование по одной текстурке на опенгл пока мы все не умрем. Но зачем искусственно ограничивать возможности прийти кэтмару и заоптимизировать всё по самые гланды для opengl 4.5 дропнув утюги и завезти тридэ, мышку и rtx лучи?

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



Top
 Profile  
 
PostPosted: 23 Mar 2023, 19:41 
Offline
Абсолютно неубиваем
User avatar

Joined: 18 Oct 2009, 04:01
Posts: 6332
Location: Владивосток
DeaDDooMER» 1) Оставляя Draw мы либо тянем на сервере часть графического стека, либо повсюду ifdef'им эти Draw
Но почему? Метод .Draw() вызывается самим рендерером, просто он передаёт туда самого себя. Сущность же не знает ничего, кроме абстрактного интерфейса рендерера.
А если рендерера нет, то и вызывать тогда этот метод будет просто некому.
И если его делать не виртуальным, то он даже спилится на этапе устранения мёртвого кода (по крайней мере, компилятор Delphi такое точно вытворяет).

DeaDDooMER» 2) Мы прибиваемся к порядку рендеринга (back-to-front, вместо front-to-back)
DeaDDooMER» 3) Мы прибиваемся к методу рендеринга (условные glBegin/glEnd, вместо VBO)
DeaDDooMER» А может и рисовать всё сам, если он может разом нарисовать 100 монстров одним вызовом opengl, вместо ста дёрганий TMonster.Draw рисующей по одной текстурке.
Разве это всё нельзя учесть в интерфейсе рендерера?

DeaDDooMER» 4) Для получения продвинутых эффектов выдумываем всякие сложные абстракции или их отсутствие (прямые вызовы опенгл)
Зато имеем по итогу код с жёсткими объектными границами между сущностями.

DeaDDooMER» Есть такой паттерн Model-View-Controller. Возьмем монстров и применим к ним такой паттерн.
Я сам думал именно про MVC когда размышлял про то, каким образом лучше всего будет отодрать ботов в перспективе. Но разве MVC не подразумевает, что все три части являются отдельными объектами?
Типа как вот сейчас у нас есть у игрока model и view в виде TPlayer и TPlayerModel соответственно, но нет controller, потому что игрок выбирает источник управления (клавиатура / сеть / ИИ бота) через пень-колоду.

_________________
Чёрный Думер, Чёрный Думер
С монстрами сражается.
Чёрный Думер, Чёрный Думер
Рокетланчер плавится.


Top
 Profile  
 
PostPosted: 23 Mar 2023, 22:12 
Offline
User avatar

Joined: 04 Feb 2010, 14:42
Posts: 632
Location: Equestria
Черный Думер wrote:
Метод .Draw() вызывается самим рендерером, просто он передаёт туда самого себя.
1) Создает иллюзию обязательности этого метода.
2) Имеет привилегированный статус (доступ к приватным полям), а рисовать будет менее эффективно (ограничен абстрактным интерфейсом). Может привести к тому что объект вообще нельзя нарисовать другой реализацией (те самые комменты "private state" не просто так появились). У такой ошибки наверняка есть какое-то научное название. Этого можно автоматически избежать когда все рисовалки равноправны.
3) Нет гарантии что компилятор его выкинет вместе со всеми ненужными зависимостями. И вообще не красиво.

Черный Думер wrote:
Разве это всё нельзя учесть в интерфейсе рендерера?
Можно. Но зачем? Для полноценного использования vbo рисовалка все равно должно быть массовой. Не метод TMonster.Draw(), а процедура g_monsters.DrawAllMonsters() или специальный под это класс или юнит. А в идеале вообще DrawEverything() рисующая все типы объектов за раз. Это большие куски рендера, а рендеру все равно лучше знать как рисовать.

Черный Думер wrote:
Зато имеем по итогу код с жёсткими объектными границами между сущностями.
Не понимаю что это нам даёт.

Черный Думер wrote:
Но разве MVC не подразумевает, что все три части являются отдельными объектами?
В идеале - да, о чем я тут и говорю.

Черный Думер wrote:
Типа как вот сейчас у нас есть у игрока model и view в виде TPlayer и TPlayerModel соответственно
Типа того.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 03:49 
Offline
User avatar

Joined: 25 Feb 2013, 13:24
Posts: 643
Черный Думер wrote:
Разве это всё нельзя учесть в интерфейсе рендерера?
учитывать в интерфейсах всё во-первых, невозможно, а во-вторых, ненужно: получается раздутая хрень, которая неудобна в использовании для любого случая. для «универсального интерфейса» тебе придётся предусматривать даже те случаи из будущего, о которых ты сегодня знать не знаешь. (подгони машину времени, кстати, хочу на вудсток сгонять!) так что единственный нормальный способ — выдать наружу всю информацию, необходимую для выбора нужной тектуры и ты пы, и никакого интерфейса к рендеру не делать вообще.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 05:02 
Offline
Абсолютно неубиваем
User avatar

Joined: 18 Oct 2009, 04:01
Posts: 6332
Location: Владивосток
DeaDDooMER wrote:
Можно. Но зачем? Для полноценного использования vbo рисовалка все равно должно быть массовой. Не метод TMonster.Draw(), а процедура g_monsters.DrawAllMonsters() или специальный под это класс или юнит. А в идеале вообще DrawEverything() рисующая все типы объектов за раз. Это большие куски рендера, а рендеру все равно лучше знать как рисовать.
Аргумент. А если такую процедуру сделать на уровне класса (т.е. как class procedure)?
Можно, конечно, и в виде процедуры g_monsters.DrawAllMonsters(), а нужные данные высунуть из TMonster при помощи private/protected видимости (такие поля, в отличие от strict private и strict protected, заодно доступны и всему модулю, в котором класс обозначен). Но это мне кажется менее чистым решением: простые процедуры и функции я бы использовал только для сугубо рутинного кода без намёков на объектность. То есть, скажем, мастырить классы вида TMathOperations с функциями вроде sin() в качестве методов - маразм и объектность ради объектности, потому что класс должен обозначать сущность, над которой оперируют. Если же речь идёт о простом разграничении ответственности в коде, то для этого модули и предназначены. Вот был бы тип Real классом - тогда другой разговор.

DeaDDooMER wrote:
Не понимаю что это нам даёт.
Это даёт нам прежде всего простой, внятный и очень читаемый код.

ketmar wrote:
учитывать в интерфейсах всё во-первых, невозможно, а во-вторых, ненужно: получается раздутая хрень, которая неудобна в использовании для любого случая. для «универсального интерфейса» тебе придётся предусматривать даже те случаи из будущего, о которых ты сегодня знать не знаешь.
Вообще всё учитывать я и не предлагал. Скорее, у меня вопрос был в том, насколько такая абстракция неявна, что для её реализации придётся заведомо косить в сторону универсальности.

_________________
Чёрный Думер, Чёрный Думер
С монстрами сражается.
Чёрный Думер, Чёрный Думер
Рокетланчер плавится.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 05:08 
Offline
User avatar

Joined: 25 Feb 2013, 13:24
Posts: 643
ну так для шиткана вообще всё иначе делать придётся, например. мало ли, вдруг кто в дф решит шиткан завезти. ещё 100500 апи появятся, совсем ебанутые. высунул наружу нужное — и всё, больше не думаешь об апях на уровне model.

в вавумчике примерно так и есть, кстати: стэйт в VEntity, а загружалка 3д-объектов и рисовалка — в рендере.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 16:08 
Offline
User avatar

Joined: 04 Feb 2010, 14:42
Posts: 632
Location: Equestria
Черный Думер wrote:
Это даёт нам прежде всего простой, внятный и очень читаемый код.
но это не точно. вынос рисовалки отдельно никак код не ухудшит, а может и улучшит (g_monsters.pas размером в 5к строк не способствует улучшению читаемости, где всё в кучке и даже больше).


Top
 Profile  
 
PostPosted: 24 Mar 2023, 16:19 
Offline
Абсолютно неубиваем
User avatar

Joined: 18 Oct 2009, 04:01
Posts: 6332
Location: Владивосток
DeaDDooMER» вынос рисовалки отдельно никак код не ухудшит, а может и улучшит (g_monsters.pas размером в 5к строк не способствует улучшению читаемости, где всё в кучке и даже больше).
Готов спорить. Модули с большим количеством кода появляются везде, где возникает намёк на сколь-нибудь сложную логику.
Но этого не надо бояться, надо просто грамотно их структурировать. А вот понимание границ ответственности в коде важнее для простоты восприятия на порядки.
Потому что я хочу чувствовать, не приходя в сознание, что если надо что-то сделать или починить в поведении монстров, то мне однозначно надо идти в g_monsters.pas и начинать оттуда. А не держать в голове таблицу заведомо пересекающихся между собой и, что главное, умозрительных областей кода ("код-который-рисует", "код-сетевой-игры", и т.д.) ради возможности гадать, к какой из них относится искомое поведение.

_________________
Чёрный Думер, Чёрный Думер
С монстрами сражается.
Чёрный Думер, Чёрный Думер
Рокетланчер плавится.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 16:41 
Offline
User avatar

Joined: 25 Feb 2013, 13:24
Posts: 643
а зачем гадать? смотрим интерфейс, жмакаем кнопочку «перейти к исходнику» — всё.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 16:44 
Offline
Абсолютно неубиваем
User avatar

Joined: 18 Oct 2009, 04:01
Posts: 6332
Location: Владивосток
ketmar» а зачем гадать? смотрим интерфейс, жмакаем кнопочку «перейти к исходнику» — всё.
Смотрим интерфейс чего? Как мне его определить сходу, если у меня эта таблица в голове ещё не сработана?

_________________
Чёрный Думер, Чёрный Думер
С монстрами сражается.
Чёрный Думер, Чёрный Думер
Рокетланчер плавится.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 17:06 
Offline
User avatar

Joined: 04 Feb 2010, 14:42
Posts: 632
Location: Equestria
Черный Думер wrote:
Готов спорить.
Ну я хз что ответить на аргумент "мне удобнее всё в кучке"


Top
 Profile  
 
PostPosted: 24 Mar 2023, 18:15 
Offline
Абсолютно неубиваем
User avatar

Joined: 18 Oct 2009, 04:01
Posts: 6332
Location: Владивосток
DeaDDooMER» Ну я хз что ответить на аргумент "мне удобнее всё в кучке"
Ну так лучше же кучки, чем размазывать всё слой за слоем по всей кодовой базе, разве нет?
Потому что кучка - это готовая выборка поведения, которую не надо делать самому.

_________________
Чёрный Думер, Чёрный Думер
С монстрами сражается.
Чёрный Думер, Чёрный Думер
Рокетланчер плавится.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 18:26 
Offline
User avatar

Joined: 25 Feb 2013, 13:24
Posts: 643
>Смотрим интерфейс чего?
объекта. в нём видим кучку интересных методов. находим тот, что шире всех улыбается — и вперёд.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 18:28 
Offline
Шерлок Холмс
User avatar

Joined: 19 Jun 2019, 23:28
Posts: 249
Location: Equestrian Wasteland.
Подход "всё держим в куче" только усложняет вкатывание потенциальных программистов и заодно осложняет работу уже имеющимся, в особенности когда требуется отлавливать баг, который может сидеть как в этой куче, так и скрываться в других. Даже грамотное позиционирование определённых блоков кода не поможет в ориентировании, а при некоторых условиях наоборот запутает.

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

_________________
aka TerminalHash.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 19:25 
Offline
Абсолютно неубиваем
User avatar

Joined: 18 Oct 2009, 04:01
Posts: 6332
Location: Владивосток
ketmar wrote:
объекта. в нём видим кучку интересных методов. находим тот, что шире всех улыбается — и вперёд.
Так у монстра в предлагаемом DeaDDooMER'ом варианте я увижу только множество высунутых наружу данных, а не методы.
Это ладно, но ведь даже в модуле с монстрами я не увижу того поведения, которое ищу, а вот это уже проблема.

FoxFromPripyat wrote:
Когда я пытался впиливать по подсказкам Прима поглощение урона, я охренел от необходимости лезть в совсем другие места ради единственной строчки, чтобы оно не выдавало ошибок компиляции и работало, при изучении некоторых коммитов охреневал вдвойне и больше от того, что там за изменения навешивали чуть ли не по всему коду игры. В особенности когда некоторые куски кода тупо повторяются в разных местах.
Так это буквально заявление в пользу того, что я описываю, разве нет?
У меня стремление всё держать во множестве отдельных, неделимых и непересекающихся кучек, а не в одной большой и многослойной.

_________________
Чёрный Думер, Чёрный Думер
С монстрами сражается.
Чёрный Думер, Чёрный Думер
Рокетланчер плавится.


Top
 Profile  
 
PostPosted: 24 Mar 2023, 21:10 
Offline
User avatar

Joined: 04 Feb 2010, 14:42
Posts: 632
Location: Equestria
Черный Думер wrote:
Ну так лучше же кучки, чем размазывать всё слой за слоем по всей кодовой базе, разве нет?
Потому что кучка - это готовая выборка поведения, которую не надо делать самому.
Для "готовой выборки поведения" есть стандартные реализации. И нет никакого размазывания.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 22 posts ]  Go to page 1, 2  Next

All times are UTC + 3 hours


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
doom2d.org, since 2007