Unity: Всё, что вы не знали о разработке – Telegram
Unity: Всё, что вы не знали о разработке
1.74K subscribers
40 photos
101 links
Авторский канал о разработке в Unity от Alex Silaev (CTO в Zillion Whales). Mushroom Wars 2 моих рук дело.
Рассказываю об интересный кейсах, делюсь лайфхаками, решениями.
Download Telegram
if lock if

У меня в BECS очень много всяких вариантов локов, например, для ресайзов вида:


if (obj.isCreated == false) {
obj = new Obj();
}


Но поскольку мне нужно многопоточность, самый простой вариант обернуть это в лок:


lock (lockObj) {
if (obj.isCreated == false) {
obj = new Obj();
}
}


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


if (obj.isCreated == false) {
lock (lockObj) {
if (obj.isCreated == false) {
obj = new Obj();
}
}
}


Получается такая матрешка, но давайте резберемся что будет для нескольких потоков:

1. Поток №1 входит в условие if (obj.isCreated == false);
2. Поток №2 тоже может успеть войти в это условие;
3. Поток №1 блокирует объект;
4. Поток №2 ожидает снятия блокировки;
5. Поток №1 создает объект и записывает его;
6. В этом месте любое количество других потоков может войти в метод и уже использовать наш объект;
7. Поток №1 выходит из блокировки, освобождая поток №2
8. Поток №2 проверяет еще раз if (obj.isCreated == false) и оказывается, что ничего делать уже не нужно.

Таким образом если какая-то операция происходит довольно редко, то такая реализация ленивой инициализации в многопоточной среде увеличит производительность.
Естественно, я не использую конструкцию lock, заменяя ее CompareExchange, но для примера привожу именно lock.

Важное уточнение: isCreated = true вашего объекта в конструкторе должен идти последним, т.е. когда мы уже можем использовать объект.

#multithreading #csharp #lock
🔥21👍8🤔4🥰3
Unity Collections

Тут что-то на днях я заглянул в UnsafeQueue и заметил, что юнитехи ее переработали, и зачем-то добавили пул. Ну вот я не знаю что у них в головах когда они так делают, но в итоге если делать new UnsafeQueue(Allocator.Temp), то эту коллекцию обязательно нужно освобождать через Dispose, иначе эти самые пулы не будут возвращены.

Тут я подумал, может я че не заметил в изменении доки?

Because a Temp allocator gets discarded as a whole, you don't need to manually deallocate Temp allocations, and doing so does nothing.


Да нет, все как и раньше: если создал Temp, то освобождать ее нет необходимости.

Короч, я очень надеюсь, что они исправят эту ошибку и вообще уберут пул, который зачем-то запихнули в Queue, при этом только туда, т.е. UnsafeList, например, остался как и был.

#unity #unsafe
🤔29👍9🔥4
SerializeReference

Я уже рассказывал про этот аттрибут, который позволяет серилизовать любую структуру. Но недавно я столкнулся с интересным багом, т.к. по мне так это именно баг:


public interface IMyData {}

[System.Serializable]
public struct MyStruct {
[SerializeReference]
public IMyData data;
public int references;
}

public MyStruct[] items;


Если добавить поле с именем references в эту же структуру где у вас будет SerializeReference, то при компиляции будет ошибка The same field name is serialized multiple times с указанием именно на поле references.

На всякий случай я заглянул в доку и не нашел там ничего про то, что нельзя использовать поле с этим именем.

#unity #bug #serializereference
🤔24🤣12🔥6👍5😁2
Assembly Definition и Assembly Definition Reference

Я уже затрагивал эту штуку несколько раз, но совсем забыл рассказать про Assembly Definition Reference.

Если Assembly Definition - это непосредственно либа, которая собирается из папки.
То Assembly Definition Reference - это продолжение этой же либы, но уже в другой папке.
Итого можно сделать несколько папок, которые будут собираться в одну библиотеку.

Это на самом деле дает возможность использовать internal классы/структуры/методы из пакетов, которые вы используете в проекте. Т.е. просто сделать указание на либу из пакета и можно спокойно использовать нужные internal методы.

#unity #asmdef #asmref
👍29🔥12🤔4❤‍🔥1
Буду на gdc, если кто-нибудь хочет встретиться - буду рад поболтать, обсудить проекты или еще что-нибудь :)
Если хотите что-нибудь конкретное узнать со стендов/докладов, могу сделать несколько постов.

#gdc2025
🔥43👍13🥰2
Unity 2d

Sprite Mask
Теперь можно использовать любой 2d renderer в качестве маски через mask source, включая tilemap.

Sprite Libraries
Выше уровнем, чем SpriteAtlas, по сути дает возможность нормально контролировать использование ресурсов.
Через Sprite Resolver можно подставлять части.

Pixel art
Поддержка aseprite, новый importer, включая анимации.

Performance
Завезли SRP Batcher.
2D light batching debugger по слоям.

Physics
Появились Layer overrides.
Simulation layers позволяют отключить слои симуляции.
Delaunay tessellation для генерации коллайдеров.
Rigidbody slide functions.

Advanced
Sprite shape geometry creator позволяет делать сложные формы.
ScriptablePacker позволяет интегрировать свой алгоритм паковки.
2D Renderer поддерживает Render Graph.

WIP
Render as 2D - позволяет рендерить 3д объекты как 2д с сортировкой, тенями, светом и пр.

#gdc2025
👍37🔥193🥰1🎉1🤗1🫡1
Performance tips & tricks

Advanced profiling
CPU Native profilers (ios Instruments, android studio, etc)
Или
Use Superluminal (подключается к юнити, показывает методы включая cpp)

CPU issues
Shader variants (их может быть тысячи)
Используйте shader prewarming
SVC имеют метод WarmUp
GraphicsStateCollections заменяют SVC в unity 6
MPB не используйте в URP /HDRP
MeshRenderer в некоторых случаях будет лучше: если рисуем одну мешку больше 1к раз, используем одинаковые свойства
Используйте RenderMeshInstanced или BRG
Draw calls: используйте HLOD
Используйте меньше камер, каждая камера имеет большой оверхед
Используйте разные SRP для разных камер
Resident Drawer
Split jobs

GPU issues
Overdraw: прозрачные объекты перекрывают друг друга, исключите полностью прозрачные объекты
Используйте renderdoc
Quad Overdraw: используйте LOD/HLOD
Shaders: исключайте if (например shader keywords), используйте half, int16

Memory
Используйте native profilers
Избегайте коротких (по времени) и длинных аллокаций вместе
Используйте unmanaged аллокации
Используйте Memory profiler memory map
Используйте GC.Alloc call stacks feature
Asset duplication - ресурсы будут загружены в память несколько раз (при использовании бандлов)

Shaders
Используйте shader_feature вместо multi_compile
Используйте IPreprocessShaders API

#gdc2025
🔥22👍10🥰1
Тут один хороший человек расписал работу моего аллокатора, который я использую в ME.ECS и ME.BECS. За что ему отдельное спасибо :)

P.S: Для чего нужен аллокатор и сорсы в этом посте: https://news.1rj.ru/str/unsafecsharp/49

#allocator #memory
🔥23👍10🤯73
Animator

На самом деле тут есть 2 момента, которые я бы хотел обозначить с точки зрения кода:
1. У аниматора есть Rebind. Используется в том случае, если анимация затрагивает объекты, которые создаются динамически, но на момент запуска аниматора их еще не было.
Например: при клике по кнопке нужно проиграть анимацию, которая висит на рут объекте, но будет затрагивать объект, который мы создаем в иерархии. Тогда перед тем как "заказывать" стейт для проигрывания у аниматора, нужно сделать animator.Rebind().
2. keepAnimatorStateOnDisable. Это такая штука, которая позволяет "сохранить" (точнее не сбрасывать) состояние аниматора при выключении и последующем включении объекта, на котором есть аниматор. На самом деле если перейти в debug-режим инспектора, то эту галочку можно включить там и без кода.

#animator #unity #animations
👍27🔥131🫡1
ME.BECS #1: Создание и инициализация проекта

@heavyfront сделал то, до чего у меня не доходили руки;) За что ему огромное спасибо!

https://youtu.be/PCdhnXEjRQI

#becs #tutorials
🔥15👍11🥰2😱2
https://youtu.be/o8nWWKiDCzw

Поговорили про структуры данных 🙂

#record #stream
🔥33👍10🥰3
ME.BECS #2: Основные возможности

https://youtu.be/5clulSSIrco

#becs #tutorials
🔥15👍4🥰2
ME.BECS #4: Query, Jobs. Network input events, transport.

https://youtu.be/dzUZorAW_uI

#tutorials #becs
🔥145👍4🥴4🥰1
Я тут на выходных посидел и все таки добил вариант с детерминированным созданием сущностей в многопоточке.
Как это работает:
1. На этапе кодогена, разбираются все джобы и в IL смотрю сколько раз вызывается создание сущности в конкретной джобе. Данные кодогенятся в специальную табличку;
2. Когда мы в коде делаем AsParallel и Schedule для джобы - если для этой джобы больше одного создания сущности за одну итерацию, то резервируем столько сущностей, сколько необходимо чтобы обеспечить несколько потоков;
3. Каждый поток знает индекс смещения и итерацию, т.е. фактически знает откуда забрать новые id сущностей и какими они будут;

Какую проблему оно решает? Представим код джобы:

var e1 = Ent.New(); // выдается id 1
var e2 = Ent.New(); // выдается id 2

Если такой код запустить в однопоточке - будет 1, потом 2.
А вот если в многопоточке, то тут как повезет, первым будет id 1, а вторым что угодно, т.к. id 2 мог забрать себе уже другой поток.
Таким образом, если я буду знать сколько ентитей нужно создать за одну итерацию, то и проблем возникнуть не должно.

Ограничением становятся только циклы. Если при разборе IL, я вижу, что создание сущности вложено в цикл, то такую джобу зашедулить многопоточно будет нельзя (будет исключение).

Если хотите подробнее про IL и каким образом я разбираю код - могу сделать отдельный пост.

#becs #news
🔥395👍4👀2