Telegram Web
Я тут на выходных посидел и все таки добил вариант с детерминированным созданием сущностей в многопоточке.
Как это работает:
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
IL или как я разбираю джобы

Что такое IL? IL (или Intermediate Language) - это промежутный язык, который еще и не рантайм, но уже и не C#.
Его смысл в том, что это плюс-минус набор инструкций, по которым можно понять суть происходящего в методе.

Как оно выглядит?

IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld int32 C::a
IL_0007: ldc.i4.s 10
IL_0009: cgt
IL_000b: stloc.0
IL_000c: ldloc.0
IL_000d: brfalse.s IL_0018

IL_000f: nop
IL_0010: ldarg.0
IL_0011: call instance void C::A()
IL_0016: nop
IL_0017: nop

IL_0018: ldarg.0
IL_0019: call instance void C::A()
IL_001e: nop
IL_001f: ldarg.0
IL_0020: call instance void C::A()
IL_0025: nop
IL_0026: ret


Чем я пользуюсь? https://github.com/chromealex/ME.BECS/tree/main/Runwww.tgoop.com/Extensions/Mono.Reflection
Это не Mono.Cecil, но для моих задач подходит отлично.
Работа этой штуки сводится к тому, чтобы разобрать инструкции метода и сложить их в плюс-минус удобный список.

Давайте разберем задачу из прошлого поста:
Нам нужно посчитать количество вызовов метода.


var count = 0;
var targetMethod = typeof(Ent).GetMethod(nameof(Ent.New), ...);
var instructions = executeJobMethod.GetInstructions();
for (int i = 0; i < instructions.Count; ++i) {
var inst = instructions[i];
{
// тут можем обработать инструкцию
if (inst.Operand is System.Reflection.MethodInfo method && method == targetMethod) {
++count;
}
}
{
if (inst.Operand is System.Reflection.MethodInfo method) {
instructions.InsertRange(i + 1, method.GetInstructions());
}
}
}


Фактически в данный момент мы посчитали количество вызовов определенного метода в джобе рекурсивно заходя в каждый метод. У меня к этому коду еще добавлены различные аттрибуты, чтобы не заходить в ненужные методы и чтобы не попасть в рекурсию, но я уверен, что вы справитесь с этой задачей.

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

Для этого нам нужно обратиться к OpCodes. Это по сути код операции у инструкции, которую мы сейчас обрабатываем.
Кодов очень много, но нас интересуют лишь коды бранчей.
Бранч - это ветка. Бранчи могут быть созданы в различных кейсах: это может быть if, может быть цикл foreach, for, while и т.д.
Код вида br* будет приводить к переходу (типа goto) к конкретной инструкции.
И тут начинается самое интересное. Любые циклы и условия - это одна и та же операция br*, но за одним исключением: вместе с этой операцией передается номер строки (например, IL_0007), куда нужно осуществить переход.
Так вот если адрес этого перехода меньше, чем адрес текущей инструкции - мы закончили итерацию цикла и возвращаемся назад.
А если инструкция br* ведет нас вперед, то это открытие условия или цикла.

В коде выше можно увидеть эту инструкцию в виде IL_000d: brfalse.s IL_0018. То есть мы переходим в 0018, что находится ниже, значит это условие, а не цикл.

Текущий код по поиску информации можно посмотреть здесь:
https://github.com/chromealex/ME.BECS/blob/a665a344e6957ff18945dd9862981d79910c0491/Editor/CodeGenerator/Generators/JobsEarlyInitCodeGenerator.cs#L437

#becs #il #jobs
35312🔥8👍3🤯3
https://github.com/chromealex/ME.Monitor/tree/main

Написал тут тулзу для мониторинга за серваками.
Умеет ping, tcp, rest, выводит графики, рисует маршруты. Предназначена для сборки в качестве standalone/adroid/ios билдов. Строит наглядно визуализацию расположения серверов и маршруты к ним. Для примера есть Config.json с рандомными сервисами, которые можно легко заменить на свои.

Проект написан на коленке и там ничего не структурировано, так что без поллитра вряд ли разберетесь. Тем не менее, вполне хорошее решение получилось и свою задачу выполняет отлично. Собираете билд, подменяете конфиг на свои серваки и вперед наблюдать.

Предложения принимаются :)

#unity #tools #monitoring
🔥23👍11🥰4😁3
Marshal.OffsetOf

С помощью этого метода я в BECS нахожу где фактически находится поле в структуре, в которое я хотел бы записать данные.
Я это использую в кодогене, когда я точно знаю название поля и хочу вписать туда данные, но использовать рефлексию для этого нет никакого желания.

Работает примерно так:

var offset = Marshal.OffsetOf(type, field.Name);
codeGenOutput.Add($"var addr = (byte*)_addressOf(ref obj) + {offset};");
codeGenOutput.Add($"*(({objType}*)addr) = new SomeStruct(...);");


В итоге получается, что мы просто записываем данные в структуру obj без рефлексии и нам не важно приватное там поле или публичное.

#unsafe #fieldoffset #marshal
🔥31🤔6👍42
https://docs.google.com/presentation/d/1LlxyWOz88aXjAQVVJdN3Fss08E8Rnu8k/edit?usp=drivesdk&ouid=113802151767136274653&rtpof=true&sd=true

Делюсь с вами моей презой для одного из закрытых ивентов. Тут собрана база, но вполне возможно, что в вашей компании/команде вы отходите от нее, чтобы делать мир лучше :)

#events #presentation #allocations
🔥43👍12🥰42😁1😨1
https://youtu.be/TilgB9G1G3g

Когда-то давно я показывал видео про наброски rts, в этом видео рассматривается основная механика построения деревьев и поиск данных в них. На ее основе можно строить физику, коллизии, RVO, поиск целей и множество других интересных вещей.

#becs #trees
👍14🔥85🥰1
CallerFilePathAttribute

Этот аттрибут позволяет получить путь к файлу, из которого происходит вызов метода:


void Sample([CallerFilePath] string file = null) {
UnityEngine.Debug.Log(file);
}


Еще обратите внимание, что есть CallerMemberName (из какого метода вызов) и CallerLineNumber (на какой строке).

#debug #csharp #code
4🔥345👍3
Player Loop

Вместо MonoBehaviour на сцене можно использовать инжект в текущий луп. Для этого нужно понимать в какой именно мы хотим добавить вызов своего метода. Всего есть 8 групп:

TimeUpdate = 0
Initialization = 1
EarlyUpdate = 2
FixedUpdate = 3
PreUpdate = 4
Update = 5
PreLateUpdate = 6
PostLateUpdate = 7


Использовать проще всего с инжектом при инициализации, но в принципе можно вызывать этот код когда угодно, как и удалять свою систему из обработки:


[RuntimeInitializeOnLoadMethod]
static void Inject() {
var loop = PlayerLoop.GetCurrentPlayerLoop();
var sys = new PlayerLoopSystem {
type = typeof(YourClassName),
updateDelegate = () => Debug.Log("Custom Update")
};
var index = 4; // PreUpdate (см список выше)
loop.subSystemList[index].subSystemList = loop.subSystemList[index].subSystemList.Append(sys).ToArray();
PlayerLoop.SetPlayerLoop(loop);
}


#unity #playerloop
3🔥51👍2
Unity.Mathematics

На самом деле многие не поняли зачем им использовать Unity.Mathematics, если у них есть Vector2/Vector3 и Mathf, например.
Давайте разберем самый простой пример:

var a = 10;
var b = 20;
var c = 30;
var result = a * b + c; // Считаем результат


У Unity.Mathematics есть math.mad, который внутри делает тоже самое:

void mad(int a, int b, int c) => a * b + c;


И самая большая ошибка считать, что никакой разницы в коде не будет. А работает это так: при компиляции кода, вызов math.mad мапится напрямую на simd-инструкцию, то есть код превращается из 2х инструкций в одну.
Пример с math.mad - это лишь пример того, где вы можете сэкономить, в целом есть всякие select еще, которые тоже все избегают использовать.

#unity #simd #math
🔥46😁21
ResourcesAPI

В юнити 6.2 появилась классная штука, которая позволяет переопределить дефолтные методы загрузки ресурсов. И это круто, если подобным образом они будут развиваться и дальше, то когда-нибудь можно будет переписать весь движок 🙂

https://docs.unity3d.com/6000.2/Documentation/ScriptReference/ResourcesAPI.html

#unity #api #resources
🔥27🤡3👍2😁1
NoAlias

Этот аттрибут используется для Burst и позволяет экономить на инструкциях.
Например:


int Method(ref int a, ref int b) {
b = 13;
a = 42;
return b;
}


В данном случае Burst не знает, пересекаются ли данные a и b (хранятся ли в памяти в одном месте или нет).
То есть тут будет 3 инструкции mov, 2 для установки значения, а третья - для чтения этого значения. Если же добавить аттрибут NoAlias, возврат данных будет без дополнительной загрузки:


int Method([NoAlias] ref int a, ref int b) {
b = 13;
a = 42;
return b;
}


Аттрибут можно использовать для NativeArray (контейнеров), которые не пересекаются, и в качестве возврата из методов [return: NoAlias]. При этом это имеет смысл, если метод не заинлайнен, т.к. при инлайне аттрибут не будет иметь смысла.

#noalias #burst #code
🔥201
Всем привет ;)

Как вы могли заметить, в этом канале нет рекламы, продажи курсов и подобных историй. Так будет продолжаться и дальше.

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

https://boosty.to/chromealex

Пишите в личку: @chromealex
5🔥75👍24🤡5👏4
Рукожопы (других слов нет) выпустили апдейт юнити, в котором отломали uitk в инспекторе.
Так что ждем хотфикс, а пока (если у вас вдруг ломается инспектор) можно отключить uitk и использовать imgui:
Project Settings -> Editor -> use IMGUI

#editor #unity
😁51👍9
Unity.Profiling.IgnoredByDeepProfilerAttribute

Аттрибут, который спасет вам время, если у вас много кода, который вы точно не хотели бы видеть в профайлере.

#attributes #profiling #unity
🔥46👍10
2025/10/17 01:04:10
Back to Top
HTML Embed Code: