Войти
Инди-ЮнитиФорум

Мастер класс по хорошему коду в юнити (28 стр)

Advanced: Тема повышенной сложности или важная.

Страницы: 123 24 25 26 27 28
#405
21:13, 1 фев. 2021

Т.е. ты не знаешь js?
Архитектору непозволительно не знать функциональные языки, а js один из самых распространенных.


#406
21:14, 1 фев. 2021

seaman может он на питоне пилит тулзы :)

#407
(Правка: 3:58) 1:04, 2 фев. 2021

Любой желающий может перечитать тред, и убедиться, что:

> а вот тут, действительно, оказывается разница где-то в 10 раз между сохранением
> текста и бинарным представлением. Это я действительно, не дооценивал.
это было неоднократно сказано и подчёркнуто мной несколько раз, но tac продолжал настаивать, что его текстовый пример всё равно быстрее. Если ты не можешь даже примерно оценить стоимость операции, то что же брался утверждать? Вопрос риторический. А после tac слился и отказался сравнивать результаты теста.

1.1
> Это может применяться в различных подзадачах - как то просто сохранение игры для игрока, различные подсохранения состояний игровых объектов (как то был пример у меня с неким манипулятором, у которого игрок может задать разные программы, и в течении игры их надо запоминать и менять), для диалогов и много где. Основной признак этого - сохранение ряда свойств разных объектов объязательно с выгрузкой на постоянный носитель - в файл или БД (причем БД, тоже может быть файловая). И не будем это путать с клонированием (!).
tac изначально спорил с позицией, что сериализация вообще нужна кому-либо на проекте без клиент-серверной архитектуры. В ранних сообщениях он прямо утверждал, что во всех случаях это можно и нужно заменить на единую реляционную БД, а иначе вы бездари.

> объязательно с выгрузкой на постоянный носитель - в файл или БД
Ложное утверждение. Для того, чтобы реализовать инспектор свойств объекта в течение игры, совершенно избыточно делать это через обращение к постоянному носителю. Только здесь уже речь идёт о потере производительности не в 10 раз, как в случае с текстовым форматом, а в 100 и более. Также существует масса контекстов, где после использования сериализации результат сохранять на постоянный носитель вообще не нужно (даже отложенно). Например, если речь идёт об изменении скорости анимаций и других настроек, привязанных к конкретной сессии; о клонировании объектов в памяти; хранении и выводе на экран сведений о сессии, например, лога ошибок; и многие другие применения.

> причем БД, тоже может быть файловая
taс изначально утверждал, что файловая база данных — это сделанная на коленке nosql-база, но нет ничего лучше, чем реляционная база данных во всех контекстах, за исключением сетевого. Идёт попытка подменить задним числом свои тезисы и присвоить себе аргументы оппонента.

> И не будем это путать с клонированием
Как было показано, в контексте клонирования объектов tac пользуется чужой сериализацией. Причём не ручной, а с применением атрибуции Unity (так работает Instantiate).

1.2
> Сохранять можно в разных форматах, два из которых наиболее важны - текстовый (как наиболее репрезентативный для разработчика), и бинарный (о котором стоит позаботится, если хочется больше скорости). И иметь возможность их преобразовывать один в другой.
Ещё одна попытка присвоить себе тезисы оппонентов. Как видно из примеров tac'а, он всегда реализует только один формат в своих проектах; о преимуществах бинарного узнал лишь в процессе этого треда. Ну а преобразование формата из одного в другой так и подавно не смог показать даже в своих поздних примерах. Несмотря на неоднократные указания на это.

1.3
> В случае баз данных в виде хранилища, тут ничего принципиально не меняется -
> это просто другой физический носитель, и в нем можно так же хранить в разных
> форматах, только добавляется еще вариант с реляционным форматом. Главное
> правильно использовать когда это нужно.
Принципиально меняется то, что становится невозможным контроль версий через VCS и одновременная работа над одними объектами. А это является неотъемлимой частью профессиональной работы в крупных командах последние лет 15.

> Когда нужен одновременный доступ к разным объектам - совершенно не правильно использовать файл
То есть всегда в профессиональных командах. Что и требовалось доказать.

> Вначале можно держать в разных файлах, или в свойствах префабов, часто этого хватает, но лучше внешние данные организовывать в БД, особенно если у вас в игре есть сеть.
Избыточно дополнительно организовывать данные в БД, если они и их история уже хранятся под VCS (см. выше). БД не добавляет никаких преимуществ.

2
> При том, что задача описанная в п.1., естественно полезная и нужная, очень вредно делать это через т.н. сериализацию, и не будем путать её с записью данных в поток (текстовый или бинарный).
Человек фактически утверждает что вещь не равна своему определению:

Сериализация (в программировании) — процесс перевода какой-либо структуры данных в последовательность байтов

Это не единственная его попытка подменить термины своими собственными определениями, чтобы исказить смысл сказанного задним числом. Но по истории легко видеть, что ему было доходчиво и подробно объяснено, что имеется в виду под сериализацией с моей стороны. Но аргументацию tac долго и упорно вёл совсем не по линии критики атрибутов, а утверждал, что глупо вообще когда-либо переводить в последовательность битов, если при этом не требуется сохранение в файл или передача по сети.

> Основным признаком современной сериализации, является то,
> что движки заставляют расставлять атрибуты в классах исходного кода,
> что совершенно не приемлемо.
Полная ерунда. Атрибуты — это всего лишь надстройка для управления процессом сериализации. Сама сериализация может происходить, как под управлением атрибуции, так и без. Отличный пример, это Protocol Buffers. В оригинальной библиотеке от Google нет никакой атрибуции. Она появляется только в библиотеке protobuf-net, который является альтернативной реализацией. Если к сериализации относить только то, где можно проставлять атрибуты, то практически все библиотеки сериализации для С++ таковыми не являются.

> что совершенно не приемлемо
Совершенно неприемлемо, это когда производительность падает в 10 раз из-за незнания архитектором основ стоимости операций. Или когда большой распределённой команде советуют использовать БД вместо VCS для контроля версий. А использование атрибутов имеет, как свои плюсы, так и минусы. В индустрии нет единого мнения, что перевешивает, это больше из области вкусов.

2.1
> Чисто стилистически атрибуты "заполняют" и смешиваются к кодом, и в этом случае доводится до абсурда использование атрибутов, в то время как атрибуты, в этом случае используются не по назначению с точки зрения ООП (но многих это почему то мало волнует, к сожалению)
Как я упоминал выше, точка зрения имеет право на существование и с кем-нибудь другим можно было бы подискутировать на тему. А с тем, у кого даже нет поддержки совместимости сохранений, и кто отрицает её необходимость, обсуждать плюсы атрибуции бессмысленно. Он их даже не поймёт, так как никогда не сталкивался с поддержкой собственных форматов.

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

> Можно, это пытаться как то выходить из положения, но как правило это адский трешак.
Мой код выложен в свободный доступ и каждый может ознакомиться с ним на предмет трешака.
Примеры кода твоей сериализации, которые ты называл оптимальным решением, тоже можно найти в этой теме.
Выводы, где адский трешак, а где нет, пусть каждый делал сам.

#408
(Правка: 1:30) 1:29, 2 фев. 2021

tac
Я предупреждал, что будут санкции, если будешь продолжать и дальше тратить моё время (бан).
На этом сайте не нужны мастер-классы от лже-гуру.

#409
20:07, 11 мар. 2021

DirectConvertor - прямые конвертации альтернатива сериализации

точку все же поставлю я, а не балтун с банххамером
#410
(Правка: 3:21) 3:13, 12 мар. 2021
чего только люди не по навыдумывают, лишь бы не учить базы данных © tac, 2018

Смотри какой путь ты проделал под моим чутким руководством: от полного отрицания сериализации, через ручные Save/Load в текстовом формате, до собственной убогонькой библиотечки сериализации! Это уже не уровень школьника/первокурсника. Я бы ожидал увидеть подобный код в исполнении среднего студента 2-3 курса технического ВУЗ'а. Это на самом деле заметный прогресс, которым ты можешь по праву гордиться.

Увы, вынужден тебя огорчить, но это всё ещё одна из худших библиотек сериализации по функционалу, удобству, производительности и потреблению памяти, что мне доводилось видеть. Тебе ещё предстоит многому научиться. Что ж, давай разбирать ошибки.

1. Косяки конкретной реализации.

1.1. Прежде всего, у тебя происходит разделение пользовательских классов на три уровня объектов:
- "корневые" объекты (которые агрегируют DirectConvert) и могут сохранять объекты;
- вложенные объекты (которые реализуют IDirectConvertData)
- встроенные типы, такие как Guid и Vector3, для которых по всей видимости у тебя написано по 4 функции (бинарная/текстовая загрузка/сохранение)

Это неправильно сразу по двум причинам. Во-первых, это жёстко привязывает структуру к выбранному уровню. Например, мы решили реализовать структуру Position3D (которая изначально содержит только x, y, z) на уровне встроенного типа. Но затем, по мере усложнения проекта, выяснилось, что эта структура также будет хранить поворот или какую-то другую информацию. И соответственно теперь мы хотим пользоваться всеми фишками библиотеки, вроде тегов и версионирования. Мы не можем просто добавить нужные поля. Нам нужно реализовывать IDirectConvertData и писать GetData/SetData вместо старых функций, пишущих напрямую.

Во-вторых, мы не можем сериализовать или просто клонировать Position3D когда захотим в один вызов, так как он является IDirectConvertData. Нам нужно либо создавать "объект-сохранятор", который агрегирует DirectConvert и реализует эту логику (по классу на каждый случай), либо же каждый раз создавать в куче новый DirectConvert по месту использования (и записывать всё в три строчки вместо одной).

Решение: для пользовательских структур/классов необходимо использовать единый универсальный механизм разметки, как он сериализуется. Как и делают во всех нормальных библиотеках сериализации.

1.2. Поговорим о производительности.
После того, как ты нам продемонстрировал конкатенацию строк через оператор "+" и искренне удивлялся, что парсинг строк — это дорогая операция в сравнении с memcpu, неудивительно, что ты продолжаешь писать позорно тормозной код в своих официальных мануалах. Я понимаю, что ты понятия не имеешь о стоимости операций в C#, но передача объектов через тип object — это дорого. Почитай на досуге про то, что такое boxing. У тебя это происходит на КАЖДОЕ поле КАЖДОГО объекта. На реальном проекте, вроде нашего, это выльется в сотни тысяч боксингов на загрузке сейвов (а может и миллионов). Стоит ли говорить о какой-то производительности после этого?

Ты много рассуждаешь о том, что рефлекшн в библиотеках сериализации с атрибуцией, якобы, дорого. Но ты не понимаешь, что рефлекшн применяется один раз на старте проекта на каждое поле каждого КЛАССА. Заметь, класса, а не инстанса. Это сотни операций. Сотни, а не сотни тысяч. И только один раз на старте проекта, когда строится мета-дата. Далее рефлексия больше не применяется (интроспекция больше не нужна, а вызовы происходят через типизированные делегаты, полученные на прошлом этапе). Даже если сохранять объекты миллионами. Ни одного дорогого вызова рефлексии больше не случится.

Я уж тем более боюсь представить, насколько неоптимально идёт обработка данных у тебя дальше (сорцы я не покупал, разумеется). Но повсеместное использование виртуальных функций тоже на пользу производительности не идёт твоей либе. Это уже не так критично, но всё равно это с треском проигрывает, например, подходу MessagePack, который вообще умеет генерировать форматеры из байт-кода. Сделай для начала честное сравнение (бенчмарк) по производительности, памяти и размеру бинарника (и скорости чтения с диска соответственно) твоей текущей версии, которую ты за деньги решил продавать (лол) с MessagePack, который бесплатный. Спойлер: судя по мануалу твой текущий код без экстренных фиксов позорно сольёт по всем показателям.

Решение: избавиться от боксинга, кэширование форматера по типу производить через Generic-синглтоны или аналогичные подходы.

1.3. Поговорим о памяти.
Что агрегация, что наследование DirectConvert — оба варианта приводят к тому, что все объекты возрастают на лишние 8 байт. Если бы это касалось только "корневых" объектов, чья семантика в твоей либе мне не совсем понятна, это можно было бы стерпеть. Но ты рекомендуешь наследовать от ConvertData даже вложенные объекты "для удобства". То есть каждый сраный AngleInfo (который сам может быть байт 8), удваивается. Если у нас, к примеру, миллион таких значений (данные звёзд на небе в космосиме), то вместо 8 мегабайт памяти, они будут занимать уже 16 мегабайт в оперативке. Это просто расточительство, причём на ровном месте. Кто-то может сказать копейки в наше время и, мол, память пользователя бесконечная; но это лишь выдаёт в вас то, что вы никогда не работали с консолями. Впрочем, кто ж к ним подпустит горе-архитекторов.

Решение: не пихать дополнительные поля в сам объект. Всю мета-информацию и данные сессии сериализатора хранить отдельно от инстанса. Убрать наследование из рекомендуемых приёмов.

1.4. Вариант с реализацией интерфейса IDirectConvertData тоже имеет проблемы. Он невозможен для внеших классов. Если в случае с атрибуцией существует возможность задать ту же информацию для внешнего класса (например, Dummy-класс с такими же именами полей) или просто руками захардкодить, то сделать чужой библиотечный класс реализующим интерфейс — уже невозможно.

Единственный способ в твоей либе поддержать сторонний класс — поддержать его как встроенный тип по аналогии с Guid или Vector3. Это соответствует кастомному форматеру в библиотеках сериализации с атрибутами, но в этих библиотеках ты можешь в этом форматере писать вложенно какие угодно поля, включая корневые, не создавая новые DirectConvert'ы, потому что нет разделения на уровни между объектами (см. пункт 1). В твоём же случае ты ограничен возможностью и функционалом встроенных типов. Либо тебе придётся создавать ещё DirectConvert'ы и передавать им все данные о настройках текущей сессии сериализации.

Решение: отказаться от реализации интерфейсов. Форматтеры/протоколы должны быть внешними по отношению к классу.

#411
3:13, 12 мар. 2021

2. Недостатки подхода.

Что ж, это были явные косяки конкретно твоего API. Предположим, что ты их исправил, следуя моим советам. Предположим, у тебя уже возрасла скорость работы; сокращено потребление памяти; у тебя появился единый способ описывать свои классы; и способ применять все фичи для чужих классов. Давай теперь поговорим о недостатке всего подхода в целом.

2.1. Многословность.

Простое сравнение:

+ атрибуция
+ САМЫЙ короткий вариант DirectConvert


2.2. Bug-prone

Достаточно ещё раз взглянуть на пример выше, чтобы понять, что это место копипаст-ошибок. Статические анализаторы именно в таких портянках находят кучу ошибок даже в коде IT-гигантов, типа Google. Чего уж говорить про простых смертных, с куда более скромными стандартами тестирования. Перепутать на загрузке местами две строчки одного типа и потом героически отлаживать — раз плюнуть.

2.3. Версионность.

Твоя версионность — это просто смешно. К примеру, мы хотим удалить Angles, но добавить новое поле. Сравнение:

+ атрибуция
+ DirectConvert

Продолжаем усложнять! Теперь меняем тип History с int на float:

+ атрибуция
+ DirectConvert

Ещё не запутались, где ">=", где "<", где "1.02", а где "1.03"? У меня уже глаза поплыли. Я вот не уверен, что даже это правильно написал. А если ещё 5-6 изменений в класс внести? И стоит поправить в поле и в GetData, но забыть/перепутать в SetData — и привет новые баги.

2.4. Отсутствие поддержки цикличных ссылок.

Отмазка про то, что узлы графа можно хранить отдельно и восстанавливать их потом — это именно что отмазка. Хорошая сериализация делает это первым делом на уровне всей сессии сериализации, а не на уровне каждого отдельного свойства. Потому что, во-первых, графов может быть куча и для каждого писать функцию конвертацию туда-сюда задолбаешься. А во-вторых, цикличные ссылки могут возникать в сложных структурах данных, по которым просто так не пройтись кодом. Например, персонаж помимо других своих полей, хранит ссылку на контейнер с его вещами, а контейнер содержит ссылки на предметы, среди которых есть автомат, который содержит поле с патронами, которые в него заряжены, а патроны хранят ссылку на список групп-объектов, на которых для них удваивается урон, а в этой группе находится персонаж, с которого мы начали. Любая уважающая себя игровая студия позволяет геймдизайнерам настраивать такие и более сложные взаимосвязи между объектами. И то, что где-то здесь возникла цикличная ссылка никак не должно влиять на возможность сохраняться. И не должно требовать написания дополнительного кода.

JSON.Net, FullSerializer, Protobuf, ODIN, моя сериализация на основе MessagePack — все они поддерживают это.
А на твой подход это в принципе не натягивается. Всё, что можно сделать с твоим подходом — это запрещать геймдизайнерам делать цикличные ссылки в сложных структурах данных и самим за этим следить. Очень профессионально, ничего не скажешь.

#412
(Правка: 3:28) 3:14, 12 мар. 2021

3. Advanced-фичи.

До сих пор мы говорили о твоей поделке в сравнении с типичными библиотеками сериализациями, вроде Protobuf-net.
Как было показано, никакой реальной альтернативы ты не предлагаешь. Это просто ещё одна мини-либа ручной сериализации, только с косяками проектирования и без ряда фичей, типа атрибуции и циклических ссылок. Но раз уж ты свой подход предлагал взамен моему фреймворку, то давай взглянем на лишь частичный список моих фич, от которых твоя поделка всё так же бесконечно далека.

3.1. GuidObject и распределённая сериализация

У меня GuidObject — это не просто возможность записать Guid в качестве значения поля. Если у меня в потоке сериализации встречается только Guid вместо полноценного GuidOjbect'а (например, персонажа), то этот Guid трактуется, как ссылка на объект. Сам объект при этом может быть записан где-то в другом файле. При загрузке объекта А из первого файла, мы можем встретить 10 ссылок на объект B и 10 ссылок на объект C. После этого мы можем загрузить другой файл с объектом B, и после его загрзуки все 10 ссылок из А в B будут указывать ровно на этот объект. А потом мы по аналогии загрузим C, который содержит ссылки на A и B. И все ссылки будут корректны, как будто бы мы загрузили всё из одного пакета. Порядок загрузки файлов, разумеется, не важен.

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

3.2. Склеивание из разных источников

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

3.3. Исключение неизменившегося

Моя сериализация помнит, из какой последовательности байт родился какой объект, и даже модуль объекта. Так, она может понять потом, какие данные не менялись, и не записывать их лишний раз в сейв игрока. Представь ситуацию, что у торговца в инвентаре был нож, а мы выпускаем обновление, в котором нож заменили на топор. Если игрок никак не успел повзаимодействовать с торговцем, то инвентарь торговца не пишется в сейв; и загрузив сохранение, игрок обнаружит, что у торговца топор (загрузится новая версия инвентаря торговца). Но если игрок уже успел поторговать, купить у торговца нож, а ему взамен отдать 10 яблок, то после обновления на этом сейве у торговца будет 10 яблок (загрузится версия инвентаря торговца из сейва игрока). Всё это определяется автоматически.

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

#413
(Правка: 3:31) 3:14, 12 мар. 2021

ИТОГ

И только не надо рассказывать, что всё, что я перечислил, одной строчкой делается в твоём коде. Ты уже 5 раз полностью переписываешь свой якобы простой и понятный пример, якобы уделывающий все либы сериализации, включая мою, а всё ещё допускаешь позорные ошибки с производительностью, и не можешь реализовать хотя бы треть моего функционала.

Шаг за шагом, на протяжении 14 страниц в 2018 и 13 страниц в 2021, я потихоньку вытаскиваю тебя из полного невежества с твоими базами данных вместо сериализации; учу тебя тому, что такое распределённая командная разработка; и почему нужен VCS со множеством файлом и текстовый мёрдж; я научил тебя тому, что бинарный формат на порядок быстрее текстового; объяснил, как работает клонирование объектов в Unity; в этом же треде тебя научили делать по-человечески конкатенацию строк; а я продолжаю объянять элементарные вещи про boxing и рефлексию. С каждым разом, твой пример становится всё больше и больше похож на обычный фреймворк сериализации. Это собственно уже, примитивная, но либа сериализации. Хоть ты и отказываешься её так называть.

Если тред продолжать дальше, то возможно через несколько страниц ты исправишь косяки, которые я перечислил в разделе 1, чтобы хотя бы не сливать на бенчмарках. Ещё через пару попыток переписывания своего примера с нуля, сделаешь-таки поддержку цикличных ссылок. Может быть тогда уже приблизишься к пониманию, что вместо переписывания по 100 раз одного и того же, можно использовать атрибуты. Хотя не факт. Так или иначе, я тебе в няньки не нанимался и давно пора прекращать этот цирк с конями (скажи спасибо, что всему предыдущему научил). Поначалу было даже весело читать твои перлы, но уже страниц 5 назад мне это надоело.

Но дело в общем-то не в этом. Я не просто так закрывал тред. Тебе чёрным по белому было сказано, что твои лже-мастер-классы на этом сайте не нужны. Был тут уже один городской сумасшедший — Jimnik. Бегал по всему форуму под разными виртуалами и проповедовал свой 3D-движок (на деле никчёмная поделка) и тоже рассказывал, что он светочь архитектуры. В какой-то момент мы решили, что хватит это терпеть, и вытравили это с форума. Так вот я тебе повторяю, что ты со своими наставлениями, гайдами и мастер-классами приравнян в правах к нему.

Хочешь делать тут свои проекты, собирать команду, нанимать людей — пожалуйста. А распространять свои бредни под видом гуру — это будет удаляться, как мусор. Или делай приписку, что это личное мнение любителя без опыта серьёзных проектов, или мне на глаза не попадайся. Ну а будешь упорствовать, играть со мной в переоткрывание тем, то просто из бана не будешь вылезать, а то и вовсе аккаунт заблокирую с концами.

Страницы: 123 24 25 26 27 28
Инди-ЮнитиФорум

Тема закрыта.