cookie

Utilizamos cookies para mejorar tu experiencia de navegación. Al hacer clic en "Aceptar todo", aceptas el uso de cookies.

avatar

Minutri | Unity

Канал о разработке на Unity, C# и GameDev в целом. Обо мне: Senior Unity developer https://t.me/minutri/2 Для связи: @MinutriNet

Mostrar más
Publicaciones publicitarias
678
Suscriptores
Sin datos24 horas
Sin datos7 días
+930 días

Carga de datos en curso...

Tasa de crecimiento de suscriptores

Carga de datos en curso...

🤓Статический анализ кода в Unity 😉 Когда команда разрастается, время на ревью у старших разработчиков, тех/тим лидов также увеличивается. 😉 В результате, то время, которые бы они потратили на условно сложные задачи, тратится на написание замечаний по стайл коду (или около того). Если это джун/стажер или просто новый член команды — таких замечаний может быть много. ❗️ Значит это нужно оптимизировать, автоматизировать. На помощь приходят инструменты статического анализа. Грамотно написанный статический анализатор предлагает наличие файлов конфигураций, изменяя которые, вы можете сделать проверку на те параметры, которые у вас утверждены в команде. Более того, если этот анализатор с открытым исходным кодом, то вы можете дописать свою логику. 🍑 Например, у меня был опыт с StyleCop. Неплохой инструмент, внедряется в юнити без особых проблем. Его же можно использовать и на стороне бека, если он у вас на шарпах. Наверняка есть инструменты лучше, например pvs, но должного опыта с ним я не имею, так что ничего толкового сказать о нем не могу. 😉 Далее идет обязательное внедрение на билд машину. На стадии препуша, после пуша или на стадии мержа мы прогоняем код в анализаторе. Если появились новые ошибки - показываем их и не даем замержить/запушить и т.д. 🔪 Поделюсь еще своим опытом: обязательно показывайте в гитхабе/гитлабе/slack/тд (в зависимости от масштабов ci) новые ошибки и места их возникновения. Даже если уже в проекте присутствуют 2000 варнингов, - отображать необходимо новые отдельно. Если будете брать разницу между количеством ошибок и просто писать, что появилось N новых - это неверный путь. Выводы: 1) Добивайтесь внедрения статического анализатора в команде на ранней стадии. Чтобы потом было не больно переходить на него. 2) Время, которое удалось сэкономить, можно потратить также на ревью. Но уже сосредоточиться на отношениях, соблюдении стандартов архитектуры, выявлении бОльших проблем, чем PascalCase вместо camelCase. 3) Хорошей практикой может быть изменение исходного кода анализатора. 🙂Каким статическим анализатором пользовались вы? Удавалось ли дописывать и изменять логику самого анализа? 😏Давайте поделимся опытом, оставляйте комментарии)
Mostrar todo...
👍 12 7🤔 3
📈 Увеличение производительности с помощью IL2CPP. SPARSEHASH Вышла интересная статья от техлида Алексея Мерзликина. В ней он рассматривает Sparse_hash_map и Dense_hash_map — форматы хранения хешмапы в плюсах. И оба они гугловские. Думаю, основной аудитории канала, обяъяснять, что такое хеш-таблица не нужно. Но напишу о двух из рассматриваемых способах хранения: 🥱 Sparse_hash_map — мало жрет памяти, но работает медленней. 🔥 Dense_hash_map — жрет памяти больше, но и работает быстрее. Метаданные в IL2CPP, сгенерированные для каждого типа и используемые для таких задач, как вызов виртуального метода, практически не проанализированы в интернете. Следовательно, это ценная информация. — А теперь к сути статьи: Дело в том, что IL2CPP использует Il2CppHashMap либо в том, либо в другом формате, в зависимости от: #define IL2CPP_USE_SPARSEHASH (IL2CPP_TARGET_ANDROID || IL2CPP_TARGET_IOS) "Это означает, что sparse hash используется для мобильных платформ, а dense — для других, таких как ПК, консоли и т. д." Для чего это сделано? Вероятней всего для того, чтобы на мобилках экономить память. Но всегда ли это нужно? Я думаю нет. — Хорошо, но что если мы принудительно в плюсовом коде изменим IL2CPP_USE_SPARSEHASH на ПК на 1 и сделаем замеры скоростей. Сравним, насколько медленней работает тот или иной вариант. Для примера, автор приводит сравнения, а также использует Zenject для своих тестов. В нем используются "reflection and instance creation", поэтому пример нагруженный, хороший. — Он получает разницу в производительности — в 2 раза. Таким образом, заключает он, что это важный параметр и в случае необходимости, можно его изменять. 👀 Что ж, я со своей стороны отмечу, что тест был не совсем корректный: Во-первых, не было проверки на мобильном(ых) устройстве(ах). Во-вторых, не было кейсов, по мимо Zenject. 🙂 Тем не менее, информация была полезна, спасибо автору за неё. Анализируя другие статьи, можно утверждать, что dense одна из наиболее быстрых реализаций (конечно, за вычетом некоторых её особенностей, касаемых, к примеру, перестроения). Следовательно, большой необходимости использовать другие варианты — нет. И это хорошо, что именно её можно использовать без особых проблем в коде, проходящем через IL2CPP. Если интересна тема IL2CPP, ставьте 👍 #thematic_post #il2cpp
Mostrar todo...

👍 39🔥 3😁 2
👀🗑Сборка мусора в Unity? Путь GC.Collect(). Давайте проследим некоторый путь. Путь GC.Collect(). Но сначала. Что такое GC.Collect()? Это принудительный вызов сборки мусора. Во что превращается GC.Collect? Ведь мы знаем, что Unity использует другой сборщик, который называется Boehm GC, а следовательно GC.Collect должен во что-то транслироваться. Чтобы ответить на этот вопрос, нужно проследить путь преобразования кода. 1️⃣ Давайте напишем простой класс (Убираю лишние переносы в целях экономии места в посте; Длинные имена, сокращены для простоты чтения ):
public class Minutri : MonoBehaviour {
    private void Start() {
        GC.Collect();
        GC.Collect(2, GCCollectionMode.Forced);
    }
}
Два разных вызова — чтобы посмотреть, куда уходит двойка, а куда GCCollectionMode.Forced. GC.Collect(2, GCCollectionMode.Forced) говорит о том, что нужно вызвать сборку вплоть до второго поколения с forced. Но не забываем — мы работаем с Boehm GC. Поэтому про работу с поколениями не может идти речи. Как же получается? Написать так можно — а поколений нет. Смотрите далее) 2️⃣ После трансляции IL2CPP находим метод Start класса Minutri. Называется он у меня Minutri_Start_... 3️⃣ В Assembly-CSharp.cpp наблюдаю, во что превратился метод:
void Minutri_Start_ (Minutri_t* __this, ) {
static bool s_Il2CppMethodInitialized; // <------ а
GC_Collect_m(NULL); // <------ б
GC_Collect_m(2, 1, NULL);
return;
Интересовать нас может сразу два пункта: а) Видим использование static bool. Напоминаю — это медленно. б) Оба метода GC_Collect раскрылись, мы их наблюдаем, и они похожи. 4️⃣ Идем глубже, в методы GC_Collect_m...
void GC_Collect_m() {
static bool s_Il2CppMethodInitialized;
L_0 = GC_get_MaxGeneration_m(NULL);
GC_InternalCollect_m(L_0, NULL);
}
а) Опять static bool. б) Воу 🤨 Все-таки получаем максимальное поколение. Получается, я вам врал! Какой ужас. (нет) в) Вызываем GC_InternalCollect_m и передаем туда "максимальное поколение". Давайте я вам помогу, и мы не будем заходить в GC_get_MaxGeneration_m. Он возвращает ноль. Но это и неважно. Сейчас все увидите. 5️⃣ Зайдем глубже, в GC_InternalCollect_m. Мы видим метод. Там опять нам всё не нужно, кроме одной строки:
(()mscorlib::System::GC::InternalCollect) (___0_generation);
Заходим в InternalCollect. Далее в Collect(generation) 6️⃣ Ну что ж. Мы пришли, поздравляю!) Метод Collect перед вами целиком:
void il2cpp::gc::GarbageCollector::Collect(int maxGeneration) {
#if IL2CPP_ENABLE_DEFERRED_GC
    if (GC_is_disabled())
        s_PendingGC = true;
#endif
    GC_gcollect();
}
Тут, как мы видим, всё: последнее упоминание поколений. Глубже мы не пойдем. Вы видите, чтобы maxGeneration параметр где-то использовался? Вот и я не вижу. Зачем они так сделали? Вероятней всего для того, чтобы в случае использования другого GC можно было быстро перейти на поколения. Так, а что у нас с GC.Collect(2, GCCollectionMode.Forced)? Там пойдут goto и прочие вещи, которые здесь не буду рассматривать, так как не хватит места в посте. Но, короче говоря: в нем будет вызов этого же Collect из пункта 6️⃣. То есть, что бы мы ни указывали в аргументы GC.Collect — это не имеет значения. Резюмируем: — GC в .NET C# поколенческий, в Unity - нет — Вызов сборки мусора конкретного поколения вызывает сборку мусора в целом — Перегрузки GC.Collect, при настройке билда в IL2CPP не влияют на перформанс и ничего не ломают #thematic_post #gc
Mostrar todo...
👍 13👏 6😁 1🙈 1
ПОЛИМОРФИЗМ СИСТЕМЫ Не то что бы мы (инженеры программисты) можем превращать воду в вино. Скорее нет ограничений как таковых в решении любой задачи. Давайте представим. У вас есть любой язык программирования. Что вы на нем НЕ сможете написать? Конечно, есть ряд проблем, которые не возможно решить за конечное время. Но это не значит, что решения нет или что его нельзя написать. Но мало того, что задач, которых нельзя решить, нет, так еще и вариаций решений одной проблемы может быть сколько угодно много. 🔹Давайте порассуждаем глобально, без привязки к языку/платформе. Для простоты возьмем самую примитивную задачу: "Вывод Hello World на экран". Сколько вариантов решений вам приходит в голову? Мне из-за проклятия знаний даже сложно представить, сколько их. Потому мой ответ: а какие ограничения? 🔸Этим всем. Я подвожу к мысли:
У любой задачи несколько решений. И мы, анализируя внешние факторы и время, склонны выбирать наиболее эффективное в данный момент.
Назовем это полем решений S (solutions). 🔸Любое решение можно проектировать по разному. При том вариантов дизайна может быть сколько угодно. Назовем это поле вариантов дизайна D (design). Например: — Под каждый char заведем свой класс. Сделаем fluent builder, который будет собирать нашу строку посимвольно. 🔸Ну и наконец у каждого решения есть поле реализаций (имплементаций). То как эта задача может быть фактически написана. Назовем это — I (Implementation). Пример: — Каждый char представим в виде ASCII кода, запишем в массив и при выводе сконвертируем коды в символы. — Или напишем свой рендер символов в консоли 🔻Отсюда 1ый принцип инженерной разработки:
Каждая задача имеет поле возможных решений, которое является произведением поля возможных реализаций и поля возможных дизайнов. - Wang, Software Engineering Foundations, p.33, Theorem 1.6
В виде формулы это будет выглядеть так: S = D * I Это подводит к вопросу, а насколько велики D, I и S? — Много велики. Потому что (D * I) -> ∞. А на поле решений влияют многие факторы, такие как: — Выбранный ЯП. — Code style. — Модели данных. — Маршалинг объектов в память. Любое изменение будет приводить к другой(-му) реализации/дизайну решения. 🔻Из этого следует очень важный вывод, от которого болит у всех:
Трудно технически и/или экономически доказать что программа имеет единственно верное рациональное решение в поле возможных решений. Это более известно как: не существует единственно верного решения - Wang, Software Engineering Foundations, p.33, Corollary 1.4
Это одно из фундаментальных ограничений, с которым мы имеем дело каждый день. А называется оно — полиморфизм системы. И ограничение это когнитивное, т.к. завязано на фундаментальное ограничение человеческого мозга (про ПРОДУКТИВНОСТЬ я уже писал) 🔸Ну и, говоря простым языком: — Любые споры, распри, негативные эмоции из-за вариантов решения не имеют смысла на фундаментальном уровне. Т.к. любая сторона не сможет доказать однозначное преимущество одного решения над другим. Об архитектуре проектов на unity я пишу в своем канале, подписывайся❤️‍🔥 Специально для канала @minutri. Ссылка на цитируемую книгу @UniArchitect #software_engineering
Mostrar todo...

👍 7🔥 4🤯 2 1👏 1🗿 1
Photo unavailableShow in Telegram
😏 IL2CPP. Static bool Если внимательно изучить код после работы IL2CPP, мы увидим использование static bool переменных в c++. Например:
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void Minutri_Start_m_ (Minutri_t_* __this, const RuntimeMethod* method) 
{
 static bool s_Il2CppMethodInitialized;
 if (!s_Il2CppMethodInitialized)
}
Что это такое? Зачем про это знать? 😉 Static - это ключевое слово в C++, используемое для придания элементу особых характеристик. Для статических элементов выделение памяти происходит только один раз, и существуют эти элементы до завершения программы. 😉 Хранятся все они не в heap и не на stack, а в специальных сегментах памяти, которые называются .data и .bss (зависит от того инициализированы статические данные или нет). Теперь обратимся к картинке. На ней схематично показаны секции: 1. Text segment 2. Initialized data segment 3. Uninitialized data segment(bss) 4. Heap 5. StackText segment 😉 Нас интересует сейчас bss. Именно в этой области содержится наша неинициализированная статическая переменная s_Il2CppMethodInitialized. 😉 Для того, чтобы обратиться к статической переменной, нам нужно сделать несколько дополнительных действий, таких как переход в другой сегмент памяти и проверка инициализации переменной. Поэтому - это медленно. 😉 Если мы не часто используем этот метод, то мы почти гарантировано получаем промах в кеше (т.е. задержку). И наоборот, если часто, то данные практически точно будут находится в кеше ЦП. "Собственно, как мне это пригодится?" Да в общем то и никак) Расширяем кругозор, узнаем новое)) Ладно, шучу (наверное). На самом деле, опять же, применение найдется в собеседованиях. Причем, я точно знаю, что в собеседовании в wargaming когда-то (N лет назад) спрашивали о сегментах памяти, какие есть помимо стека и кучи. #thematic_post #il2cpp
Mostrar todo...
👍 12 4🤔 4🔥 1🐳 1🙉 1
🤓 Аргументы методов В ходе обсуждения значимого и ссылочного типов, встает вопрос о месте их хранения. Про значимый тип в аргументах методов говорят, что его экземпляр хранится на стеке. Так ли это? Небольшая справка: —— 😉 Скорость обращения к регистру ЦП значительно выше, чем к тем данным, которые лежат на стеке. 😉 Регистры подключены напрямую к ALU ЦП, поэтому, для упрощения, - обращение к регистрам требует один такт. 😉 С другой стороны, как только мы достаем данные из стека, все усложняется. В работу подключаются кеши процессора, их контроллеры, шины памяти, оперативная память. Операция занимает значительно больше времени. С точки зрения программы, данные можно поместить в ОЗУ или в регистр. «Записать в кеш» нельзя. 😍Естественно, что при возможности, лучше работать с регистрами, так как мы ускорим работу программы. Дак вот, в C# имеет место быть следующая особенность. 🤓CLR via C#: «Кстати, в некоторых процессорных архитектурах для повышения производительности аргументы передаются через регистры». 🤔Пойдем дальше. Действительно ли по результатам работы il2cpp все значимые данные в методах хранятся на стеке? Нет, насколько я помню, они могут храниться и в куче. На самом деле во многом это зависит от реализации. Это, наверное, наиболее правильный ответ на вопрос о том, где будет храниться экземпляр значимого типа в аргументах. Но без объяснения он может звучать так, что кандидат не разбирается в теме. Поэтому, важно рассказать подробнее об этом. Думаю, будет полезно👊 Ставьте 👍
Mostrar todo...
👍 30 5🔥 2
Photo unavailableShow in Telegram
🤨Некоторые кейсы работы с памятью в Unity, которые встречались на практике. Часть 1 1. DoTween В нем разработчики используют pool Tweneers, поэтому проблем с аллоцированием твинеров нет. А вот в конструкциях типа: camera.DOShakeRotation или Transform.DOMoveZ там есть некоторое выделение памяти после использования. Каждая анимация DoTween выделяет мусор. Как правило это не критично. Оптимизацию следует начинать не с этого. Но знать об этом — кажется полезным. Для решения можно использовать кеширование:
private Tween _cameraShakeTween;  
private void Awake() {
_cameraShakeTween = camera.DOShakePosition(duration, strength).SetAutoKill(false).Pause();  
}  
public void ShakeCamera() => _cameraShakeTween.Restart();
У этого метода кэширования есть свой недостаток: приостановленные анимации по-прежнему получают обновления каждый кадр, потребляя память и циклы ЦП. 2. Большая нагрузка Если геймплей активный, насыщен персонажами, предметами и прочим, то из-за GC могут происходить подвисания. Даже с использованием инкрементной сборки. Хорошим решением может послужить выключение автоматической сборки: GC: GarbageCollector.Mode.Manual. Перед началом сцены, загрузки схватки или иной итерации геймплея выключить автоматический GC. Включить ручную сборку после окончания этой сессии. Из документации: "Лучше всего отключать сборщик мусора только для долгосрочных выделений. Например, вы можете выделить всю необходимую память для уровня вашей игры до его загрузки, а затем отключить сборщик мусора, чтобы избежать снижения производительности во время уровня. После завершения уровня и освобождения всей памяти вы можете снова включить сборщик мусора и использовать его System.GC.Collectдля освобождения памяти перед загрузкой следующего уровня"
Mostrar todo...
🔥 25👍 9
🤓CI/CD. Зачем нужно непрерывное развертывание (CD)? Целью CD является доставка измененной версии приложения в эксплуатацию. Из книги: В отделе маркетинга одного предприятия — назовем его «Big Money Online Commerce Inc.» — решили пересмотреть процедуру регистрации на сайте своего интернет-магазина. Целью было привлечение новых клиентов и увеличение объема продаж. 🛑Из-за отсутствия CD возникли проблемы: — на рабочем окружении оказались конфиги для теста, а не для релиза. — после кодефриза (по всей видимости), кто-то закинул вместе с небольшим исправлением что-то еще. В результате у клиентов не открывалось окно регистрации, и не понятно вообще, сколько денег потеряла компания. И это только одно развертывание. Насколько вероятно, что проблемы возникнут и в следующий раз? ⚡️CD предотвращает подобные проблемы различными мерами. 1) Развертывание осуществляется чаще — вплоть до нескольких раз в день. 2) Частые развертывания также ускоряют получение отзывов на новые особенности и изменения в коде. Разработчикам не приходится вспоминать, что делалось в прошлом месяце. 3) Чтобы развертывание протекало быстрее, создание тестового окружения и собственно тестирование должны осуществляться автоматически. 4) Автоматизация улучшает воспроизводимость: если тестовое окружение было благополучно создано, ту же автоматизированную процедуру можно использовать для создания рабочего окружения практически с той же конфигурацией. Как следствие, проблемы, вызванные ошибками в конфигурации, не будут возникать в рабочем окружении. 5) Автоматизация дает больше гибкости. Тестовые окружения можно создавать по мере необходимости, под задачи. 6) Автоматизация самого тестирования. 7) Риски, связанные с установкой новых версий, существенно уменьшаются за счет такой настройки процедуры развертывания в рабочем окружении, которая дает возможность легко откатиться к старой версии. 8) Приложения находятся под постоянным «присмотром» — мониторингом, поэтому неожиданная остановка любого процесса, например отвечающего за регистрацию, не останется незамеченной. 👊Переместимся в геймдев, Unity. Тут важно посмотреть процессы и продукт. Сначала поговорим о бОльшей доле продуктов Unity — мобильные игры. В них бОльшая доля — кеж, гиперкеж. 1️⃣Что для них наиболее важное? Быстрая разработка, быстрая проверка на игроках. Не заходит — идем дальше, по кругу. Заходит — отлично, работаем. Как поможет CD. Для игр, которые выстреливают — сразу нужно наладить поставку. Поэтому, рекомендация: наладить процесс поставки сразу для всей компании (отдела) и по аналогии распространять на другие игры, так как этот процесс будет постоянным. 2️⃣Для всех других видов игр и не игр вовсе. Когда продукт выходит из фазы прототипирования, стиль CI должен быть схож. А вот CD отличается. Если для игр это будут поставки в Play Market, App Store, Steam и др, то вот с продуктами (например тренажерами) все по другому. Там вообще могут быть отдельные машины, сервера, docker. Сложности в безопасности, ключах, версиях, лицензиях. Если это приложения (условно оконные), то там третья история. Рассуждать можно долго, но как факт — в каждом конкретном случае нужны свои решения и толковые специалисты. #thematic_post | #cicd
Mostrar todo...
👍 8 3🤔 2
🤓CI/CD. CI. Не уходите домой, когда есть нерабочая сборка Из книги: В пятницу, в 17:30, все ваши коллеги дружно собирают вещи, а вы только что зафиксировали ваши изменения. [Пайплайн собирается долго, поэтому настроили на ночь] Сборка терпит крах. У вас есть три варианта: 1️⃣Вы можете остаться допоздна и попытаться устранить проблему. 2️⃣Второй вариант — вы отменяете изменения и повторяете попытку регистрации в следующий понедельник. 3️⃣Третий вариант — вы оставляете все как есть и уходите домой, оставляя сборку в нерабочем состоянии. Если вы оставите сборку испорченной, то к понедельнику многое забудете о сделанных изменениях, и вам понадобится намного больше времени, чтобы понять и устранить проблему. Что-нибудь можно вообще не вспомнить. Если вы будете не первым, кто пришел в понедельник утром, другие люди столкнутся с нерабочей сборкой, и их труд будет перечеркнут. Если же вы еще и заболеете за выходные, ожидайте телефонных звонков и приготовьтесь к неприятным объяснениям на тему, как вы испортили приложение, или к тому, что ваши изменения будут бесцеремонно выброшены коллегами. Эффект от нерабочей сборки, особенно испорченной в конце дня, усиливается в распределенных командах, разбросанных по разным часовым поясам. В этом случае оставить сборку в нерабочем состоянии — наиболее верный способ оттолкнуть от себя коллег. НО: вы не обязаны оставаться на рабочем месте до поздней ночи. В это время ваша работоспособность заметно снижается, и вы наделаете еще больше ошибок. ✔️Нужно регистрировать изменения регулярно, чтобы оставалось время исправить проблему, если она возникнет. ✔️Можно также отложить регистрацию изменений на следующий день. Многие опытные разработчики придерживаются правила, запрещающего регистрировать изменения позже, чем за час до конца рабочего дня. Эту операцию они выполняют следующим утром в первую очередь. Это работает и в Unity. Пример: 1) Дорефакторим код чтобы залить и смержить на недельке. 2) Отлично. Прошли ревью. Но вот беда: в последний момент не зашел в плеймод и не увидел, что в рантайме игра крашится. Но довольный ушел на выходные, что закрыл задачу. 3) 🤬Приходим в понедельник — получаем люлей. Где-то в первую очередь от тестеров, где-то от менеджеров, а где-то от коллеги разработчка, который подмержил себе изменения и уже успел сделать хотфикс. Придерживайтесь рекомендации, описанной выше! #thematic_post | #cicd
Mostrar todo...
10🔥 5
CoreCLR из Unity 6 and beyond: A roadmap of Unity Engine and services | GDC 2024 Laurent Gilbert: "Говоря о повышении производительности для 100% проектов на Unity, я рад сообщить вам, что мы добились значительного прогресса в переходе на CoreCLR. Это еще шаг в модернизации основ движка Unity. Мир .Net быстро меняется и с каждой новой версией от Microsoft происходит повышение производительности. 🔼Благодаря нашей работе над интеграцией CoreCLR в Unity мы увеличим скорость редактора, ускорим загрузку кода. Это приведет к значительному сокращению времени итерации. 🔝С другой стороны новая оптимизация и GC значительно повысят производительность во время выполнения. Некоторые тесты с UI Toolkit работают в 2 раза быстрее. " Сказали в самом конце видео о CoreCLR и достаточно быстро пробежались. Поэтому вообще не уверен, что будет готово что-то похожее на юзальное в этом году. А пока, кто следит за новинками, советую попробовать Unity 6. #thematic_post | #gc | #news
Mostrar todo...
👍 16 4
Elige un Plan Diferente

Tu plan actual sólo permite el análisis de 5 canales. Para obtener más, elige otro plan.