Новости BECS
Как мне тут сказали в чатике по ецс: "У тебя фреймворк не многопоточный, т.к. при использовании компонентов в двух джобах у тебя не будет исключения о race condition". Ну сказано - сделано. Теперь есть.
А теперь подробнее с какими трудностями пришлось столкнуться при реализации.
У меня есть 2 интерфейса:
Первый фильтрует по аспектам, второй - по компонентам. Для реализации исключений на самом деле нужно реализовать NativeContainer + m_Safety поле. При ините джобы юнити сама ищет все поля m_Safety в структуре джобы и использует их для понимания что от чего зависит.
В BECS реализация джоб выглядит примерно так:
Т.е. не нужно никаких создавать дополнительных полей. И тут внимательные читатели спросят "у тебя же ref для компонента, а как же права RO/WO/RW?". На самом деле ref тут исключительно для удобства, магия происходит на уровне кодогена.
А именно: когда вы написали код метода Execute, я его разбираю и нахожу все обращения ко всем компонентам и соотвественно могу выяснить что вы с ним делаете: например, только читаете или только пишите или и то и другое.
В этом разборе я составляю список используемых компонентов для конкретной джобы.
Если с компонетами можно было так не заморачиваться, то с аспектами так не выйдет, т.к. внутри аспекта по сути может быть 10 компонентов, а джобе вы используете только 1 или 2, например. Таким образом 2 параллельно запущенные джобы не дали бы обращаться к одному аспекту в параллель, т.к. внутри был бы лок на все компоненты. Поэтому и пришла идея парсить код метода Execute на предмет фактического использования компонентов.
В итоге кодоген создает вот такие данные для каждой джобы
Если код Execute будет таким:
Естественно, можно использовать любые вызовы внутри Execute и рекрусивно по ним BECS пройдет и увидит любые обращения.
Пришлось, конечно, скармливать JobDebugDataXXX вместо обычной джобы, что занимает чуть больше времени, чем обычно, но для этого есть ENABLE_UNITY_COLLECTIONS_CHECKS и ENABLE_BECS_COLLECTIONS_CHECKS дефайны, чтобы отключать всю эту штуку.
#becs #ecs #IL #codegenerator
Как мне тут сказали в чатике по ецс: "У тебя фреймворк не многопоточный, т.к. при использовании компонентов в двух джобах у тебя не будет исключения о race condition". Ну сказано - сделано. Теперь есть.
А теперь подробнее с какими трудностями пришлось столкнуться при реализации.
У меня есть 2 интерфейса:
IJobParallelForAspect<T..n>
IJobParallelForComponents<T..n>
Первый фильтрует по аспектам, второй - по компонентам. Для реализации исключений на самом деле нужно реализовать NativeContainer + m_Safety поле. При ините джобы юнити сама ищет все поля m_Safety в структуре джобы и использует их для понимания что от чего зависит.
В BECS реализация джоб выглядит примерно так:
public struct Job : IJobParallelForComponents<C1, C2> {
public void Execute(in JobInfo jobInfo, in Ent ent, ref C1 c1, ref C2 c2) {...}
}
Т.е. не нужно никаких создавать дополнительных полей. И тут внимательные читатели спросят "у тебя же ref для компонента, а как же права RO/WO/RW?". На самом деле ref тут исключительно для удобства, магия происходит на уровне кодогена.
А именно: когда вы написали код метода Execute, я его разбираю и нахожу все обращения ко всем компонентам и соотвественно могу выяснить что вы с ним делаете: например, только читаете или только пишите или и то и другое.
В этом разборе я составляю список используемых компонентов для конкретной джобы.
Если с компонетами можно было так не заморачиваться, то с аспектами так не выйдет, т.к. внутри аспекта по сути может быть 10 компонентов, а джобе вы используете только 1 или 2, например. Таким образом 2 параллельно запущенные джобы не дали бы обращаться к одному аспекту в параллель, т.к. внутри был бы лок на все компоненты. Поэтому и пришла идея парсить код метода Execute на предмет фактического использования компонентов.
В итоге кодоген создает вот такие данные для каждой джобы
public struct JobDebugDataXXX {
[NativeDisableUnsafePtrRestriction] public MyJob jobData;
[NativeDisableUnsafePtrRestriction] public CommandBuffer* buffer;
public RefRW<C1> c0;
public SafetyComponentContainerRO<C1> C1;
public SafetyComponentContainerWO<C2> C2;
public SafetyComponentContainerRO<ParentComponent> ParentComponent;
}
Если код Execute будет таким:
void Execute(in JobInfo jobInfo, in Ent ent, ref C1 c1) {
ent.GetParent().Get<C2>().data = c1.data;
}
Естественно, можно использовать любые вызовы внутри Execute и рекрусивно по ним BECS пройдет и увидит любые обращения.
Пришлось, конечно, скармливать JobDebugDataXXX вместо обычной джобы, что занимает чуть больше времени, чем обычно, но для этого есть ENABLE_UNITY_COLLECTIONS_CHECKS и ENABLE_BECS_COLLECTIONS_CHECKS дефайны, чтобы отключать всю эту штуку.
#becs #ecs #IL #codegenerator
👍12🥰11🤯9❤3🔥3
if lock if
У меня в BECS очень много всяких вариантов локов, например, для ресайзов вида:
Но поскольку мне нужно многопоточность, самый простой вариант обернуть это в лок:
Но поскольку операция создания объекта происходит крайне редко (тут даже скорее лучше бы подошел пример с ресайзом массива), то какой смысл тратить перф на блокировку? Никакого.
Получается такая матрешка, но давайте резберемся что будет для нескольких потоков:
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
У меня в 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
https://www.youtube.com/watch?v=eQODSTApGrU
Поговорили тут немного про использование unsafe.
#record #stream
Поговорили тут немного про использование unsafe.
#record #stream
YouTube
unsafecsharp: Как и для чего использовать Unsafe в C#
Разберем примеры unsafe кода, примеры использования
Телеграм канал: @unsafecsharp
Телеграм канал: @unsafecsharp
🔥36👍10💋3🥰2
Unity Collections
Тут что-то на днях я заглянул в UnsafeQueue и заметил, что юнитехи ее переработали, и зачем-то добавили пул. Ну вот я не знаю что у них в головах когда они так делают, но в итоге если делать new UnsafeQueue(Allocator.Temp), то эту коллекцию обязательно нужно освобождать через Dispose, иначе эти самые пулы не будут возвращены.
Тут я подумал, может я че не заметил в изменении доки?
Да нет, все как и раньше: если создал Temp, то освобождать ее нет необходимости.
Короч, я очень надеюсь, что они исправят эту ошибку и вообще уберут пул, который зачем-то запихнули в Queue, при этом только туда, т.е. UnsafeList, например, остался как и был.
#unity #unsafe
Тут что-то на днях я заглянул в 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
https://www.youtube.com/watch?v=s0ABebPu4Vg
Поговорили немного про многопоточность, блокировки и прочие штуки 🙂
#record #stream
Поговорили немного про многопоточность, блокировки и прочие штуки 🙂
#record #stream
YouTube
unsafecsharp: Многопоточность и как ее использовать
Разберем что же такое lock, как избежать блокировок, поговорим немного о cache line и как же работать с несколькими потоками.
Телеграм канал: @unsafecsharp
Телеграм канал: @unsafecsharp
🔥25❤3👍3💅3🍓1💋1
SerializeReference
Я уже рассказывал про этот аттрибут, который позволяет серилизовать любую структуру. Но недавно я столкнулся с интересным багом, т.к. по мне так это именно баг:
Если добавить поле с именем
На всякий случай я заглянул в доку и не нашел там ничего про то, что нельзя использовать поле с этим именем.
#unity #bug #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
Если хотите что-нибудь конкретное узнать со стендов/докладов, могу сделать несколько постов.
#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
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🔥19❤3🥰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
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://www.tgoop.com/unsafecsharp/49
#allocator #memory
P.S: Для чего нужен аллокатор и сорсы в этом посте: https://www.tgoop.com/unsafecsharp/49
#allocator #memory
🔥23👍10🤯7❤3
Animator
На самом деле тут есть 2 момента, которые я бы хотел обозначить с точки зрения кода:
1. У аниматора есть
Например: при клике по кнопке нужно проиграть анимацию, которая висит на рут объекте, но будет затрагивать объект, который мы создаем в иерархии. Тогда перед тем как "заказывать" стейт для проигрывания у аниматора, нужно сделать animator.Rebind().
2.
#animator #unity #animations
На самом деле тут есть 2 момента, которые я бы хотел обозначить с точки зрения кода:
1. У аниматора есть
Rebind
. Используется в том случае, если анимация затрагивает объекты, которые создаются динамически, но на момент запуска аниматора их еще не было. Например: при клике по кнопке нужно проиграть анимацию, которая висит на рут объекте, но будет затрагивать объект, который мы создаем в иерархии. Тогда перед тем как "заказывать" стейт для проигрывания у аниматора, нужно сделать animator.Rebind().
2.
keepAnimatorStateOnDisable
. Это такая штука, которая позволяет "сохранить" (точнее не сбрасывать) состояние аниматора при выключении и последующем включении объекта, на котором есть аниматор. На самом деле если перейти в debug-режим инспектора, то эту галочку можно включить там и без кода.#animator #unity #animations
👍27🔥13❤1🫡1
ME.BECS #1: Создание и инициализация проекта
@heavyfront сделал то, до чего у меня не доходили руки;) За что ему огромное спасибо!
https://youtu.be/PCdhnXEjRQI
#becs #tutorials
@heavyfront сделал то, до чего у меня не доходили руки;) За что ему огромное спасибо!
https://youtu.be/PCdhnXEjRQI
#becs #tutorials
YouTube
ME.BECS #1: Создание и инициализация проекта
С чего начать и как инициализировать проект в ME.BECS.
Телеграм канал: https://www.tgoop.com/unsafecsharp
csc.rsp:
-define:EXCEPTIONS_CONTEXT
-define:EXCEPTIONS_THREAD_SAFE
-define:EXCEPTIONS_COLLECTIONS
-define:EXCEPTIONS_COMMAND_BUFFER
-define:EXCEPTIONS_ENTITIES…
Телеграм канал: https://www.tgoop.com/unsafecsharp
csc.rsp:
-define:EXCEPTIONS_CONTEXT
-define:EXCEPTIONS_THREAD_SAFE
-define:EXCEPTIONS_COLLECTIONS
-define:EXCEPTIONS_COMMAND_BUFFER
-define:EXCEPTIONS_ENTITIES…
🔥15👍11🥰2😱2
UI.Windows
Рассказываю об основных возможностях UI.Windows
https://youtu.be/S6qLvaY204M
#tutorials #uiws #ui
Рассказываю об основных возможностях UI.Windows
https://youtu.be/S6qLvaY204M
#tutorials #uiws #ui
YouTube
unsafecsharp: Как использовать Unity UI.Windows
Как использовать UI.Windows, основные возможности
Телеграм канал: @unsafecsharp
https://github.com/chromealex/UI.Windows-submodule
Телеграм канал: @unsafecsharp
https://github.com/chromealex/UI.Windows-submodule
🔥16👍7❤4