o2 dev
108 subscribers
49 photos
4 videos
25 files
54 links
About o2 engine development
加入频道
image_2023-08-16_19-36-49.png
19.9 KB
когда рефакторишь какое-нибудь serious shit
o2 dev
image_2023-08-16_19-36-49.png
Пару слов о "грандиозном рефакторинге". Его целью была переработка системы слоев в движке. Эта итак большая задача зацепила еще одну большую задачу - переработка ивентов акторов и компонент, по типу OnStart, Awake из юнити.
Казалось бы, что в них сложного. Особенно, когда пользуешься Unity3D, они воспринимаются как что-то нативное и само собой разумеющееся. Ну просто есть эти функции, их можно использовать.

Но когда сам реализуешь все эти ивенты, становится понятно какая работа происходит "под капотом", и чего стоят эти простые методы. На самом деле у них есть нюансы: OnStart вызывается на первом Update, раньше всех Awake. Всякие OnDestroy, OnEnabled/Disabled тоже имеют свои нюансы.

Рассмотрим такую последовательность вызовов во времени жизни актора/компоненты на сцене:
OnInitialized -> OnAddToScene -> OnEnabled -> OnStart -> OnUpdate -> OnDisabled -> OnRemoveFromScene -> OnDestroy
В какой момент какой вызывать? Окей, начнем с простого, OnInitialized. "Ну это просто, из конструктора вызвал да и все" - можно подумать. А теперь рассмотрим подробнее.

Есть базовый класс Actor, от него наследуется, например, Widget. При конструировании Widget, сначала вызовется конструктор базового класса, Actor'а. Из которого мы попытаемся вызвать OnInitialized. Но проблема в том, что в это время класса Widget все еще нет, тк в данный момент конструируется класс Actor. В итоге перегружать OnInitialized нет смысла, тк объект просто не готов. Значит его нужно как-то вызвать "извне".

То же самое и с OnDestroy. Если его вызывать из деструктора ~Actor, то в этот момент части класса Widget уже попросту нет, и ничего там задестроить не получится.

Выход таков - копить во внешнем контейнере список создавшихся и удаляемых акторов. Сама сцена отлично подходит, и в ней есть метод Update, в котором можно перебрать список и вызвать OnInitialized/OnDestroy перед вызовом Update у иерархии акторов.

Окей, поехали дальше - OnStart. Тут тоже не прокатит вызвать в OnInitialized, тк мы держим в голове правило "OnStart вызывается всегда после OnInitialized". Делаем так же, храним отдельный список, формируем его сразу же из списка OnInitialized и так же вызываем перед апдейтом всей сцены
С остальными - OnAddToScene/RemoveFromScene; OnEnabled/OnDisabled - просто очень осторожно и аккуратно делаем логику вызовов. Накосячить с несовпадением пары вызово OnEnabled/Disabled довольно легко, нужно быть довольно внимательным
А што там со слоями? Ну, они как раз таки опираются на эту систему ивентов. У компоненты есть примерно такой же список ивентов. Для слоев самыми значимыми являются OnEnabled/Disabled и OnAddToScene/RemoveFromScene. В них происходит "регистрация" и "разрегистрация" объекта из списка отрисовки.

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

Если же явно указан слой и индекс сортировки, то объект помещается в общий список, который сортируется по этим индексам.

Звучит все просто, но делал полтора месяца :D

Осталось довести до ума окошко с порядком отрисовки, чтобы оно работало без багов и поддерживало drag'n'drop.
И я, конечно же, не мог пройти мимо ситуации с Unity3D 💩

Если кто-то не в курсе, они ввели "комиссию за установки". То есть если твоя игра превышает некое количество установок, то за каждую новую ты отсаливаешь сколько-то центов. Цифры офигенные, для Unity Technologies. Но печальные для всех ее пользователей...

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

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

Почему так получилось? Столько лет плодотворного сотрудничества, и вот такое... Что ж, столько лет и разработка движка была убыточной. Да, они на пике заработка, у них 2млрд в год. Однако отчет за отчетом они показывают крупные убытки. И все это - разработка движка. Если рекламная часть бизнеса дешева и стабильно приносит высокий доход (более половины от всего), то за движок они получают меньшую часть, при этом тратя огромное количество денег на разработку. Вот они и решили что "нужно больше золота".

Однако, камон, 3000 инженеров? Хм, я вроде бы немного разбираюсь в разработке движков, делал в плейрикс, свой вот делаю... Но я искренне не понимаю что там делает 3000 человек, при том что по факту в движке годами не меняются ключевые проблемы и подходы. Это голословно, однако я для своих проектов на работе мог бы взять версию 7ми летней давности, и практически ничего не потерять из новых версий.

Этот дисбаланс и пытаются покрыть юнитеки, вместо того чтобы подумать головой и потратить меньшее количество людей на достижение большего результата
Что это значит для всех остальных? Все по классике, "не стоить класть все яйца в одну корзину". Я всегда говорил чисто юнити-программистам, что не стоит быть в рамках одной технологии. Текущие события - тому подтверждение. Внезапно, технология, оказавшееся монополистом, может одновременно всем причинить боль. Внезапно огромный остров стабильности может крякнуть и начать разваливаться.

Я бы в этой ситуации посоветовал быть более униварсальным. Посмотреть на другие технологии и движки. Если это геймдев - то обязательно С++. Это уже давно стандарт индустрии, и все серьезные дядьки на нем сидят.

Даже если с юнити все будет окей (а я думаю что все будет окей, никто не хочет убивать дойную корову), то весьма полезно будет узнать плюсы, OpenGL, и вообще понять как там "под капотом".

Ну и продолжаем наблюдать дальше как разворачиваются события с Unity3D и кушать поп-корм 🍿
пару слов о том, как классная идея может классно зафейлиться.
Долго вынашивал идею, как-то применить GPT для массовых задач в кодинге. Одна из таких идей - документация. Бесячая ведь вещь, писать тонну текста. Другая идея - применить GPT для какого-то рефакторинга кода. Бывает ведь так, что нужно что-то отрефакторить, по сути простое, но муторное. Как раз с движком подвернулась такая задача.

Я сейчас перевожу сырые указатели на умные, а точнее на свою версию интрузивного шареного указателя (с опциональным сборщиком мусора для дебага). И вот это капец какая тупая работа: нужно прошерстить весь код, заменить сырые указатели на шаблонную обертку, и поменять места взаимодействия. Иногда нужно использовать слабые ссылки, когда ссылка идет "наверх" к паренту, овенру и т.п. В общем, много рутины, при этом напрягается две извилины мозга.

И вот я решил поизучать openai API, чтобы попробовать автоматизировать эту рутину. И закинул в него 10 баксов. Посмотрим что осталось...
image_2024-02-03_15-07-57.png
10.6 KB
4.5 доллара ушло на работу роботов
В общем, для меня было все довольно просто. Я наваял простой питон-скрипт (с помощью того же GPT), который шерстил сорцы в указанной папке и прогонял их через промпт. Вот такой промпт я применял:

replace raw pointers with Ref<>, except void* pointers.
Ref<> is a smart pointer like shared_ptr. Use const Ref<>& for function arguments with pointer types, also in overridden methods.
Dont replace void* pointers!! Remove default nullptr value for class members with type Ref<>.
If class variable is some kind of "parent", use WeakRef<>.

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

Поэтому пришлось довольно топорно делить на куски - до 5000 символов. Да, получается может обрубиться кусок кода на непонятно чем. Но выискивать конец функции или класса - уже как минимум не тривиальная задача, а мне было лень

Затем, конечно же уперся во время выполнения запроса. Для сорцов его можно ждать минуты. Параллельные запросы сразу уперлись в лимиты. В общем, нашел более-менее приемлимый вариант и запустил
test_gpt.py - PetStory - Visual Studio Code 2024-0.png
92.8 KB
полтора часа проворачивались сорцы только редактора
штош, зато эти полтора часа я был занят чем-то другим. Однако, количество сорцов весьма небольшое, буквально пару-тройку мегабайт. Что совсем мало по сравнению с серьезными проектами. У меня на работе, например, сорцы одной игры - это десятки мегабайт
● test_gpt.py - PetStory - Visual Studio Code 2024.png
424 KB
окей, посмотрим на сам скрипт. В целом, он весьма простой
ну а теперь самое интересное - результаты!
При тренировке промта я пробовал разные куски кода через Copilot. Выходило, ну нормально. Но прогнав целые сорцы, результат меня разочаровал
Fork 2024-02-03 14.59.06.png
194.3 KB
вот пример где сработало хорошо
Fork 2024-02-03 14.58.29.png
68.8 KB
вот тоже, хоть и опечатку сверху оставил
Fork 2024-02-03 14.59.27.png
185.4 KB
во многих местах решил поменять табы на пробелы. Я пробовал ему явно это запрещать, но наверное он тоже считает что те кто ставит пробелы, зарабатывают больше