TGViewer
Unity: Всё, что вы не знали о разработке Unity: Всё, что вы не знали о разработке @unsafecsharp · 1.74K subscribers
Post #238 2.86K
Новости BECS

Как мне тут сказали в чатике по ецс: "У тебя фреймворк не многопоточный, т.к. при использовании компонентов в двух джобах у тебя не будет исключения о 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
More from @unsafecsharp
  1. Mar 2, 2026Век живи - век учись Я вот только сегодня узнал, что оказывается можно писать не только та…
  2. Feb 10, 2026Overlay У юнити, начиная с 2022.2, появилась возможность рисовать свои окна в SceneView, п…
  3. Dec 20, 2025Удобное API Вы наверняка сталкивались с такой проблемой: void Method<T>(System.Action<T> c…
  4. Dec 7, 2025Простые лайфхаки Часто встречаю в разных проектах подобные конструкции (на самом деле обыч…
  5. Nov 18, 2025Довольно интересная задачка попалась на собеседовании в гугле: Есть матрица (массив массив…
  6. Oct 12, 2025Unity.Profiling.IgnoredByDeepProfilerAttribute Аттрибут, который спасет вам время, если у…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →