Начала работать над блокингом сцены: расставляю вначале крупные детали, потом перейду к средним, и в конце уже к мелким. 👏
Заодно дописываю генерацию, так как появляются разные небольшие моменты, о которых раньше и не предполагала) Например, для будущих камушков и скал на склонах надо учитывать нормаль, а не просто рандомно поворачивать🤔
#generation #demo_scene
Заодно дописываю генерацию, так как появляются разные небольшие моменты, о которых раньше и не предполагала) Например, для будущих камушков и скал на склонах надо учитывать нормаль, а не просто рандомно поворачивать
#generation #demo_scene
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤3🔥2💋2🆒2
Когда настраиваешь освещение/пост-процессинг/цвета, то очень быстро глаза "замыливаются" 🧂 Я с этим борюсь тем, что переключаюсь на другую задачу, и через некоторое время снова возвращаюсь к настройке.
Что получилось настроить на текущий момент внутренними средствами Unity по освещению и пост-процессингу можно увидеть на скриншотах🙌
Где:
1️⃣ Совсем без освещения и пост-процессинга.
2️⃣ Промежуточный вариант освещения и пост-процессинга. Уже лучше, но цвета тусклые + оттенок слишком сильно уходит в зеленый.
3️⃣ Текущий вариант. Цвета стали сочнее, зеленый оттенок стал меньше.
Это все еще не окончательный вариант, через некоторое время вернусь и попробую получить результат еще лучше и ближе к тому, что я хочу)
#demo_scene
Что получилось настроить на текущий момент внутренними средствами Unity по освещению и пост-процессингу можно увидеть на скриншотах
Где:
1️⃣ Совсем без освещения и пост-процессинга.
2️⃣ Промежуточный вариант освещения и пост-процессинга. Уже лучше, но цвета тусклые + оттенок слишком сильно уходит в зеленый.
3️⃣ Текущий вариант. Цвета стали сочнее, зеленый оттенок стал меньше.
Это все еще не окончательный вариант, через некоторое время вернусь и попробую получить результат еще лучше и ближе к тому, что я хочу)
#demo_scene
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍7🔥6👏2💅2
Детерминированность окружения
С этой взрослой жизнью и взрослыми делами совсем не получается выделить время на проект😭 Но дела рано или поздно заканчиваются, и часок-другой я все равно стараюсь найти)
Во-первых, ¡Hola! всем, кто меня читает, и muchas gracias!👋
Во-вторых, я столкнулась с небольшим нюансом, о котором хочу рассказать. Ранее я уже показывала немного видосиков и картиночек генерации мира с объектами окружения. Тогда мне казалось, что я решила задачу, но как это обычно бывает - я ошибалась.
Проблема:
При генерации объектов окружения самая сложная задача - это правильно вычислить уникальный детерминированный идентификатор объекта, по которому можно идентифицировать этот самый объект как в сохранениях мира (если есть изменения), так и между инстансами игры в мультиплеере.
Я привязывалась к мешу ландшафта в чанках: переводила координаты вершин в пространство вокселей и от этого считала идентификатор. Это будет работать, если во всех чанках мира только один уровень детализации (LOD), в ином случае - получаем недетерминированность. Из-за того, что на разных LOD меш содержит разные треугольники, можно получить некоторую вариативность в позиции в пространстве вокселей. Что приводит к неправильному положению объектов при смене LOD, неправильному идентификатору и дублированию объектов, невозможности связать данные сохранения и текущего мира и кучка других проблем.
Как решить?
Я пришла только к одному решению, которое будет работать всегда: считать позицию объектов окружения только в пространстве вокселей, причем всегда в LOD0. Это означает, что необходимо "догенеривать" воксели для чанков, у которых LOD выше 0. Ну, что ж, это плата за детерминированность.🧂
На данный момент тестирую этот подход, посмотрим чуть позже, что получится😶
#generation
С этой взрослой жизнью и взрослыми делами совсем не получается выделить время на проект
Во-первых, ¡Hola! всем, кто меня читает, и muchas gracias!
Во-вторых, я столкнулась с небольшим нюансом, о котором хочу рассказать. Ранее я уже показывала немного видосиков и картиночек генерации мира с объектами окружения. Тогда мне казалось, что я решила задачу, но как это обычно бывает - я ошибалась.
Проблема:
При генерации объектов окружения самая сложная задача - это правильно вычислить уникальный детерминированный идентификатор объекта, по которому можно идентифицировать этот самый объект как в сохранениях мира (если есть изменения), так и между инстансами игры в мультиплеере.
Я привязывалась к мешу ландшафта в чанках: переводила координаты вершин в пространство вокселей и от этого считала идентификатор. Это будет работать, если во всех чанках мира только один уровень детализации (LOD), в ином случае - получаем недетерминированность. Из-за того, что на разных LOD меш содержит разные треугольники, можно получить некоторую вариативность в позиции в пространстве вокселей. Что приводит к неправильному положению объектов при смене LOD, неправильному идентификатору и дублированию объектов, невозможности связать данные сохранения и текущего мира и кучка других проблем.
Как решить?
Я пришла только к одному решению, которое будет работать всегда: считать позицию объектов окружения только в пространстве вокселей, причем всегда в LOD0. Это означает, что необходимо "догенеривать" воксели для чанков, у которых LOD выше 0. Ну, что ж, это плата за детерминированность.
На данный момент тестирую этот подход, посмотрим чуть позже, что получится
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16❤5👏2⚡1🔥1
Иии, всем привет! 💃
Наверное многие уже решили, что я забыла про канал и больше не будет постов🫥 Спешу обрадовать - это не так! 🥔
Последние несколько месяцев были очень тяжелыми и насыщенными, и я просто не успевала заниматься еще и проектом. Иногда такое случается, но все это время я все равно старалась хотя бы подумать над тем, что и как должно работать и выглядеть.🙌
И, с января, я продолжу разработку и начну, пусть не очень часто, но снова писать посты💃 Планов полно, идей море, багов, которые ждут, что их пофиксят, еще больше! Заняться точно есть чем)
Всех с наступающими праздниками и хорошего начала года!🥰
Наверное многие уже решили, что я забыла про канал и больше не будет постов
Последние несколько месяцев были очень тяжелыми и насыщенными, и я просто не успевала заниматься еще и проектом. Иногда такое случается, но все это время я все равно старалась хотя бы подумать над тем, что и как должно работать и выглядеть.
И, с января, я продолжу разработку и начну, пусть не очень часто, но снова писать посты
Всех с наступающими праздниками и хорошего начала года!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥26🎄16❤6❤🔥2💋1
Всем привет! 💃
Я вернулась к коду, и первое, что я поняла: как же сложно вспоминать, на чем остановилась, спустя несколько месяцев!🔥
Но ничего, свежий взгляд, многое становится более явным, заодно какая никакая, но проверка временем🥔 Некоторые решения в коде теперь кажутся какими-то…странными что ли? 😛
Как вы помните, и как я вспомнила, я остановилась на проверке варианта «догенерации» вокселей в биоме по требованию, чтобы можно было правильно посчитать положение объектов на ландшафте. Самая большая проблема в том, что там много-много unsafe кода, и часть даже не обернуто во что-то более понятное😤
Ах, сама виновата🧂
#generation
Я вернулась к коду, и первое, что я поняла: как же сложно вспоминать, на чем остановилась, спустя несколько месяцев!
Но ничего, свежий взгляд, многое становится более явным, заодно какая никакая, но проверка временем
Как вы помните, и как я вспомнила, я остановилась на проверке варианта «догенерации» вокселей в биоме по требованию, чтобы можно было правильно посчитать положение объектов на ландшафте. Самая большая проблема в том, что там много-много unsafe кода, и часть даже не обернуто во что-то более понятное
Ах, сама виновата
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥25😁8❤7💯5🆒3
Всем привет! 🥰
Столкнулась тут с интересной штуковинкой: многие знают такой замечательный инструмент, как Profiler Analyzer, который жизненно необходим во время оптимизирования кода.
Так вот, давайте представим, что мы запускаем какую-то параллельную джобу (IJobFor). Нуу, допустим, 1000 раз:
Причем, мы хотим, чтобы вызовы Execute у нас не батчились, поэтому ставим циферку 1 вторым аргументом.
И что же я вижу в Profiler Analyzer? Count 15? И как это считать?🤔
Ваши идеи?🚪
Столкнулась тут с интересной штуковинкой: многие знают такой замечательный инструмент, как Profiler Analyzer, который жизненно необходим во время оптимизирования кода.
Так вот, давайте представим, что мы запускаем какую-то параллельную джобу (IJobFor). Нуу, допустим, 1000 раз:
var jobHandle = job.ScheduleParallel(1000, 1, default);
Причем, мы хотим, чтобы вызовы Execute у нас не батчились, поэтому ставим циферку 1 вторым аргументом.
И что же я вижу в Profiler Analyzer? Count 15? И как это считать?
Ваши идеи?
Please open Telegram to view this post
VIEW IN TELEGRAM
🦄7❤5🤔4⚡1❤🔥1
Ну, что ж, пришло время расти над собой 👏
В начале своего пути разработки генерации мира я не понимала многих концепций и часто считала одно лучше другого не понимая до конца какие-либо детали.
Например, я считала, что AoS (array of structures) хорошо подходит для хранения вокселей. Это утверждение правильно только в одном случае: если работа с вокселями происходит сразу со всеми данными в этой структуре одновременно.
Конечно, изначально в структурке Voxel я хранила только значение SDF. Но, код меняется, а значит пора пересмотреть некоторые вещи.
Я начала хранить больше информации на каждый воксель: добавился тип вокселя, веса для материалов, дополнительные флаги и прочее. И, как оказалось в итоге, каждое поле из этой структуры необходимо только для конкретных алгоритмов моей генерации. Я никогда не читаю целый воксель из памяти для того, чтобы поработать со всеми данными.
А что это значит? Пора переходить на SoA! Да! Structure of Arrays становится лучше для моей задачи! Но почему?
Давайте обратимся к циферкам.📝
1️⃣ Представим, что у нас есть чанк с 32³ вокселями (32 768 всего).
2️⃣ Размер кэш линии в среднем сейчас 64 байта (есть отличия между AMD и Intel в некоторых линейках CPU, но пренебрегаем).
3️⃣ Нам необходимо прочитать все воксели и взять их значение SDF.
🆘 AoS (Array of Structures), когда у нас одна структура , например SDF и тип вокселя:
- на один воксель 8 байт: 4 байта (SDF) + 1 байт (тип) + 3 байта для выравнивания
- один cache line помещает 8 вокселей: 64 байта делим на 8
- что в худшем случае дает нам 4 096 кэш миссов! (32 768 делим на 8) Худший случай - нам каждый раз нужен воксель из другого места.
✅ SoA (Structure of Arrays), когда мы значения храним в разных массивах:
- у нас отдельно лежит SDF, это 4 байта
- cache line вмещает 16 вокселей: 64 делим на 4
- что в худшем случае дает нам примерно 2000 кэш миссов: 32 768 делим на 16.
Разница большая!🗣
Конечно это просто цифры. В реальности ситуация не настолько плоха, мы часто читаем ближайшие элементы, из текущего кэш лайна. Да и кэш лайнов не одна штука. Но прикинуть "в худшем случае" бывает полезно, чтобы оценить, на сколько эффективно хранение данных у нас.
В начале своего пути разработки генерации мира я не понимала многих концепций и часто считала одно лучше другого не понимая до конца какие-либо детали.
Например, я считала, что AoS (array of structures) хорошо подходит для хранения вокселей. Это утверждение правильно только в одном случае: если работа с вокселями происходит сразу со всеми данными в этой структуре одновременно.
Конечно, изначально в структурке Voxel я хранила только значение SDF. Но, код меняется, а значит пора пересмотреть некоторые вещи.
Я начала хранить больше информации на каждый воксель: добавился тип вокселя, веса для материалов, дополнительные флаги и прочее. И, как оказалось в итоге, каждое поле из этой структуры необходимо только для конкретных алгоритмов моей генерации. Я никогда не читаю целый воксель из памяти для того, чтобы поработать со всеми данными.
А что это значит? Пора переходить на SoA! Да! Structure of Arrays становится лучше для моей задачи! Но почему?
Давайте обратимся к циферкам.
1️⃣ Представим, что у нас есть чанк с 32³ вокселями (32 768 всего).
2️⃣ Размер кэш линии в среднем сейчас 64 байта (есть отличия между AMD и Intel в некоторых линейках CPU, но пренебрегаем).
3️⃣ Нам необходимо прочитать все воксели и взять их значение SDF.
🆘 AoS (Array of Structures), когда у нас одна структура , например SDF и тип вокселя:
- на один воксель 8 байт: 4 байта (SDF) + 1 байт (тип) + 3 байта для выравнивания
- один cache line помещает 8 вокселей: 64 байта делим на 8
- что в худшем случае дает нам 4 096 кэш миссов! (32 768 делим на 8) Худший случай - нам каждый раз нужен воксель из другого места.
✅ SoA (Structure of Arrays), когда мы значения храним в разных массивах:
- у нас отдельно лежит SDF, это 4 байта
- cache line вмещает 16 вокселей: 64 делим на 4
- что в худшем случае дает нам примерно 2000 кэш миссов: 32 768 делим на 16.
Разница большая!
Конечно это просто цифры. В реальности ситуация не настолько плоха, мы часто читаем ближайшие элементы, из текущего кэш лайна. Да и кэш лайнов не одна штука. Но прикинуть "в худшем случае" бывает полезно, чтобы оценить, на сколько эффективно хранение данных у нас.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥26👏7🥰6
Неделя работы с памятью объявляется открытой! 🙌
У Алекса появился пост про память. Очень полезный в контексте Unity для разработчиков всех уровней.🥹 Кто не видел - очень советую глянуть его презентацию.
Я же хотела рассказать про такой аспект памяти, как ее фрагментация.
О том, как аллоцируются наши кусочки памяти, откуда они берутся и как потом возвращаются - мы очень редко задумываемся. Часто нас интересует сам факт аллоцирования, не больше. Но что происходит, когда мы "отпускаем" ненужный блок памяти? Он просто помечается как неиспользуемый.
Что будет, если мы захотим аллоцировать кусочек побольше? Мы не получим блок, который "освободили" на предыдущем шаге, так как он слишком маленький. Мы получим другой, более большой блок.🤔
А как же тот, маленький?😭 А он остается, и совсем не факт, что когда либо будет использован. И такие блоки, разного размера, которые "чуть-чуть" не подходят могут постоянно появляться, создавая множество дырок. Память становится как "решето", мы начинаем требовать все больше физической памяти RAM, а найти нужный свободный блок становится все тяжелее.
Это та самая фрагментация. И с этим надо что-то делать.📝
1️⃣ Например, использовать пулы памяти.
Выделяем большой кусок памяти, режем его на нужные нам кусочки одинакового размера и переиспользуем. Самый простой способ, который при этом очень эффективный.
2️⃣ Аллокаторы. Можно реализовать свои аллокаторы (arena, slab и другие) или взять готовые. Они фактически тоже пулы "под капотом", только более умные и позволяют использовать блоки памяти разного размера.
3️⃣ Аллокаторы с слиянием (coalescing). Еще более умная штуковина с этапом слияния свободных участков и последующим переиспользованием. Используется не так часто, потому что требует дополнительного прохода "упаковки" (да, кое-кого напоминает).
А как же аллокаторы от Unity?
- Temp и Temp Job: обычные arena аллокаторы.
- Persistent: это уже malloc/free и частые аллоцирования приводят к фрагментации.😔
P.S.: в комментариях уточнили, что persistent - это TLSF аллокатор
У Алекса появился пост про память. Очень полезный в контексте Unity для разработчиков всех уровней.
Я же хотела рассказать про такой аспект памяти, как ее фрагментация.
О том, как аллоцируются наши кусочки памяти, откуда они берутся и как потом возвращаются - мы очень редко задумываемся. Часто нас интересует сам факт аллоцирования, не больше. Но что происходит, когда мы "отпускаем" ненужный блок памяти? Он просто помечается как неиспользуемый.
Что будет, если мы захотим аллоцировать кусочек побольше? Мы не получим блок, который "освободили" на предыдущем шаге, так как он слишком маленький. Мы получим другой, более большой блок.
А как же тот, маленький?
Это та самая фрагментация. И с этим надо что-то делать.
1️⃣ Например, использовать пулы памяти.
Выделяем большой кусок памяти, режем его на нужные нам кусочки одинакового размера и переиспользуем. Самый простой способ, который при этом очень эффективный.
2️⃣ Аллокаторы. Можно реализовать свои аллокаторы (arena, slab и другие) или взять готовые. Они фактически тоже пулы "под капотом", только более умные и позволяют использовать блоки памяти разного размера.
3️⃣ Аллокаторы с слиянием (coalescing). Еще более умная штуковина с этапом слияния свободных участков и последующим переиспользованием. Используется не так часто, потому что требует дополнительного прохода "упаковки" (да, кое-кого напоминает).
А как же аллокаторы от Unity?
- Temp и Temp Job: обычные arena аллокаторы.
- Persistent: это уже malloc/free и частые аллоцирования приводят к фрагментации.
P.S.: в комментариях уточнили, что persistent - это TLSF аллокатор
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
Unity: Всё, что вы не знали о разработке
https://docs.google.com/presentation/d/1LlxyWOz88aXjAQVVJdN3Fss08E8Rnu8k/edit?usp=drivesdk&ouid=113802151767136274653&rtpof=true&sd=true
Делюсь с вами моей презой для одного из закрытых ивентов. Тут собрана база, но вполне возможно, что в вашей компании/команде…
Делюсь с вами моей презой для одного из закрытых ивентов. Тут собрана база, но вполне возможно, что в вашей компании/команде…
❤17👀4👍2
Всем привет! 👋
Продолжим тему с аллокаторами. Как я чуть раньше писала, аллокаторы решают часть проблем при работе с памятью.
Но какие аллокаторы бывают?🤔 Приведу несколько самых распространенных типов, которые часто используют при разработке игр.
1️⃣ Arena Allocator
Арена-аллокатор - это такой аллокатор, при котором вся память выделяется большим единым куском. А потом она разрезается на кусочки, которые уже используются.
Пример псевдокода:
Когда используется:
🔘 когда все элементы живут одинаково долго
🔘 когда можно разом выделить память и освободить ее
🔘 когда не надо индивидуальное освобождение объектов
TempJob в Unity является именно Arena-аллокатором.
2️⃣ Slab Allocator
Slab-аллокатор, он же пул памяти, при котором память тоже выделяется большим куском, но нарезается сразу на N участков одинакового размера. Это позволяет переиспользовать участки, если какие-то объекты больше их не используют.
Пример псевдокода для аллоцирования:
Псевдокод для возврата:
Когда используется:
🔘 когда много объектов одного размера
🔘 когда важно выделять для них память
🔘 когда важно при этом освобождать память и ее переиспользовать
Пост уже получился достаточно большим. Расскажу о других видах в следующий раз!🥔
#csharp #allocators
Продолжим тему с аллокаторами. Как я чуть раньше писала, аллокаторы решают часть проблем при работе с памятью.
Но какие аллокаторы бывают?
1️⃣ Arena Allocator
Арена-аллокатор - это такой аллокатор, при котором вся память выделяется большим единым куском. А потом она разрезается на кусочки, которые уже используются.
Пример псевдокода:
byte* memory = memalloc(1024*1024); // выделяем сразу большой кусок
byte* current = memory; // сохраняем указатель
...
byte* block = current;
current = current + blockSize; // сдвигаем указатель для следующей аллокации
return block;
Когда используется:
🔘 когда все элементы живут одинаково долго
🔘 когда можно разом выделить память и освободить ее
🔘 когда не надо индивидуальное освобождение объектов
TempJob в Unity является именно Arena-аллокатором.
2️⃣ Slab Allocator
Slab-аллокатор, он же пул памяти, при котором память тоже выделяется большим куском, но нарезается сразу на N участков одинакового размера. Это позволяет переиспользовать участки, если какие-то объекты больше их не используют.
Пример псевдокода для аллоцирования:
byte* memory = memalloc(1024*1024); // выделяем сразу большой кусок
int* freeList; // список свободных элементов
int freeCount = 0;
int allocatedCount = 0;
...
if(freeCount > 0)
{
int idx = freeList[--freeCount];
return memory + idx * blockSize; // выдаем из пула
}
if(allocatedCount < capacity)
return memory + (allocatedCount++) * blockSize; // выдаем следующий не аллоцированный
return null; // блоков больше нет
Псевдокод для возврата:
int idx = (ptr - memory) / blockSize; // вычисляем индекс блока по адресу памяти
freeList[freeCount++] = idx; // записываем индекс как свободный
Когда используется:
🔘 когда много объектов одного размера
🔘 когда важно выделять для них память
🔘 когда важно при этом освобождать память и ее переиспользовать
Пост уже получился достаточно большим. Расскажу о других видах в следующий раз!
#csharp #allocators
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
Infinity World (Дневники разработчицы)
Неделя работы с памятью объявляется открытой! 🙌
У Алекса появился пост про память. Очень полезный в контексте Unity для разработчиков всех уровней. 🥹 Кто не видел - очень советую глянуть его презентацию.
Я же хотела рассказать про такой аспект памяти, как…
У Алекса появился пост про память. Очень полезный в контексте Unity для разработчиков всех уровней. 🥹 Кто не видел - очень советую глянуть его презентацию.
Я же хотела рассказать про такой аспект памяти, как…
🔥24❤5💋4🥰2💘2
Всем привет! 👋
В прошлый раз я рассказала о нескольких типах аллокаторов, которые часто используются в геймдеве. Сегодня хочу продолжить эту тему и рассказать немного еще об аллокаторах, вернее об одном, который уже затрагивали в комментариях.📝
Two-Level Segregate Fit (TLSF) аллокатор
Он же Persistent аллокатор в Unity в DOTS. Из названия понятно, что этот аллокатор двухуровневый. В чем это проявляется?🚪
Идея
Основная идея TLSF - классифицировать все запросы на блоки памяти по размеру этих блоков и хранить их на двух уровнях.
Первый уровень, он же First Level Index (FLI), это грубое приближение классификации по размеру. Например, если нам нужен блок размером 60 байт, то FLI будет равен 5 (он же индекс корзины).
Как же считается этот FLI?
По размеру!🗣 Берется размер, вот эти 60 байт, переводится в двоичный вид (111100) и берется старший бит (32). Это определяет диапазон [32...63]. Или же степень двойки 2^5 = 32.
Следующим шагом, необходимо уточнить, в какой подкорзине относится наш запрос. Такая подкорзина называется Second Level Index (SLI). Для наших 60 байт это будет 14.
Как считается SLI?
На основе FLI и нашего размера!✨ В начале вычисляется шаг. Формула простая:
А потом и сам SLI:
Алгоритм
Для выделения куска памяти необходимого размера алгоритм можно описать вот так:
1️⃣ Смотрим на размер блока, который нам нужен и вычисляем FLI и SLI.
2️⃣ Ищем свободный блок по вычисленным FLI и SLI. Тут применяется bitmap для быстрого поиска (O(1)).
3️⃣ Если по указанным FLI/SLI нет свободного блока, идем в следующий SLI (или FLI, если в текущем закончились SLI), в котором есть, пусть и более большой по размеру.
4️⃣ Если мы взяли блок не из своего FLI (то есть он большой для нас), то режем его. А остаток возвращаем.
5️⃣ Обновляем bitmap: указываем, что FLI/SLI пусты.
6️⃣ Возвращаем выбранный блок пользователю.
Поговорим немного про bitmap.
У каждого уровня (FLI/SLI) есть своя bitmap (обычно unsigned int). Каждый бит в bitmap показывает полно/пусто в конкретном FLI/SLI. Причем для FLI каждый бит показывает именно статус конкретных FLI, а для SLI - SLI (запутанно как-то🤔 )
Когда мы аллоцируем или возвращаем, то зная FLI/SLI, можем установить или очистить нужные биты:
Чтобы найти свободный блок, нам надо вычислить маску по FLI/SLI. Например, FLI = 5, а SLI = 7:
В mask будут только биты ≥ 7. Если маска не равна нулю, то берем первый установленный бит. Если равна - ищем дальше, причем тоже за O(1).😛
А что делать с остатком, если мы взяли кусок из более большого FLI и поделили его?🫥
Остаток в данном кейсе уходит в FLI/SLI по своему размеру как свободный. Например, нужно было 60 байт, а ближайший свободный блок только из корзины (FLI/SLI) в 128 байт. 60 байт мы взяли себе, а 68 байт поместили в FLI/SLI для корзины с диапазоном [64, 128], так как FLI/SLI вычисляется точно по размеру. И для 68 байт это будет FLI = 6 (диапазон [64..127]), а SLI = 1.
Уф, хотела кратенько, а получилось довольно объемно🫥
Пишите в комментариях, а что вы используете?🥰
#csharp #allocators
В прошлый раз я рассказала о нескольких типах аллокаторов, которые часто используются в геймдеве. Сегодня хочу продолжить эту тему и рассказать немного еще об аллокаторах, вернее об одном, который уже затрагивали в комментариях.
Two-Level Segregate Fit (TLSF) аллокатор
Он же Persistent аллокатор в Unity в DOTS. Из названия понятно, что этот аллокатор двухуровневый. В чем это проявляется?
Идея
Основная идея TLSF - классифицировать все запросы на блоки памяти по размеру этих блоков и хранить их на двух уровнях.
Первый уровень, он же First Level Index (FLI), это грубое приближение классификации по размеру. Например, если нам нужен блок размером 60 байт, то FLI будет равен 5 (он же индекс корзины).
Как же считается этот FLI?
По размеру!
Следующим шагом, необходимо уточнить, в какой подкорзине относится наш запрос. Такая подкорзина называется Second Level Index (SLI). Для наших 60 байт это будет 14.
Как считается SLI?
На основе FLI и нашего размера!
step = (верхняя граница диапазона - нижняя + 1) / 16
А потом и сам SLI:
SLI = (size - base) / step
Алгоритм
Для выделения куска памяти необходимого размера алгоритм можно описать вот так:
1️⃣ Смотрим на размер блока, который нам нужен и вычисляем FLI и SLI.
2️⃣ Ищем свободный блок по вычисленным FLI и SLI. Тут применяется bitmap для быстрого поиска (O(1)).
3️⃣ Если по указанным FLI/SLI нет свободного блока, идем в следующий SLI (или FLI, если в текущем закончились SLI), в котором есть, пусть и более большой по размеру.
4️⃣ Если мы взяли блок не из своего FLI (то есть он большой для нас), то режем его. А остаток возвращаем.
5️⃣ Обновляем bitmap: указываем, что FLI/SLI пусты.
6️⃣ Возвращаем выбранный блок пользователю.
Поговорим немного про bitmap.
У каждого уровня (FLI/SLI) есть своя bitmap (обычно unsigned int). Каждый бит в bitmap показывает полно/пусто в конкретном FLI/SLI. Причем для FLI каждый бит показывает именно статус конкретных FLI, а для SLI - SLI (запутанно как-то
Когда мы аллоцируем или возвращаем, то зная FLI/SLI, можем установить или очистить нужные биты:
slBitmap[fli] |= (1u << sli); // ставим бит для SLI
flBitmap |= (1u << fli);
...
slBitmap[fli] &= ~(1u << sli); // сбрасываем бит для SLI
if (slBitmap[fli] == 0)
flBitmap &= ~(1u << fli); // если весь FLI пуст, сбросить и его бит
Чтобы найти свободный блок, нам надо вычислить маску по FLI/SLI. Например, FLI = 5, а SLI = 7:
uint mask = slBitmap[5] & (~0u << 7);
В mask будут только биты ≥ 7. Если маска не равна нулю, то берем первый установленный бит. Если равна - ищем дальше, причем тоже за O(1).
uint maskFL = flBitmap & (~0u << (5+1)); // находим другой свободный FLI
if (maskFL != 0) {
int newFli = BitOperations.TrailingZeroCount(maskFL);
int newSli = BitOperations.TrailingZeroCount(slBitmap[newFli]);
...
}
А что делать с остатком, если мы взяли кусок из более большого FLI и поделили его?
Остаток в данном кейсе уходит в FLI/SLI по своему размеру как свободный. Например, нужно было 60 байт, а ближайший свободный блок только из корзины (FLI/SLI) в 128 байт. 60 байт мы взяли себе, а 68 байт поместили в FLI/SLI для корзины с диапазоном [64, 128], так как FLI/SLI вычисляется точно по размеру. И для 68 байт это будет FLI = 6 (диапазон [64..127]), а SLI = 1.
Уф, хотела кратенько, а получилось довольно объемно
Пишите в комментариях, а что вы используете?
#csharp #allocators
Please open Telegram to view this post
VIEW IN TELEGRAM
1❤19💘5😱4🤩3🍓3
Продолжу тему с TLSF аллокатором, о котором рассказала в предыдущем посте.
Главная непонятная тема - как происходит процесс упаковки свободных блоков?🤔 Расскажу об одном из алгоритмов (их несколько для TLSF).
Если в общих чертах, то следующим образом:
1️⃣ При освобождении блока помечаем его как свободный
2️⃣ Пытаемся объединить с соседними свободными
3️⃣ Если получилось, то переносим получившийся (более большой) блок в нужный FLI/SLI
А что такое соседние свободные блоки? Тут под ними понимаются не блоки, которые лежат рядом в корзинке! Совсем нет!🗣
У каждого блока есть заголовок - это какая-то служебная (meta) информация. Часто там хранится размер блока, всякие флаги, индексы, и, указатели на соседние блоки. Когда мы делим большой блок на несколько маленьких (откусываем кусочек), то мы создаем двусвязный список, чтобы потом можно было этот кусочек вернуть обратно в большой пирог.
А что такое двусвязный список? Это ссылки на соседей "слева" и "справа".
Поэтому алгоритм упаковки достаточно простой:
1️⃣ При освобождении блока помечаем его как свободный
2️⃣ Смотрим на соседей, вдруг они тоже свободные
3️⃣ Если свободные, то объединяем в более большой блок
4️⃣ Удаляем объединенные блоки из корзин FLI/SLI, в которых они хранились
5️⃣ Сохраняем новый большой блок в новую корзину FLI/SLI
Вуаля, упаковка произошла, дырок больше нет!✨
Весь алгоритм выполняется за O(1): проверка на свободный блок - через операции с битмаской, поиск соседей - через двусвязный список, операции удаления/вставки - опять же немного пошаманить с битмаской и сохранить в нужную корзину.
Когда лучше всего использовать TLSF-аллокатор?
🔘 Нужны блоки памяти достаточно большого размера с длинным жизненным циклом (всякие долгоживущие массивы, которые хранятся в памяти, например, все время жизни игры)
🔘 Блоки разного размера
🔘 Нужна поддержка возврата и переиспользования (обычная арена не подходит)
В следующих постах расскажу о своей реализации аллокатора для вокселей: какой, почему именно такой, как я решила проблему с многопоточностью (та еще задачка🧂 ), плюс небольшой бонус! 👏
P.S.: мем от ChatGPT прилагается😛
#csharp #allocators
Главная непонятная тема - как происходит процесс упаковки свободных блоков?
Если в общих чертах, то следующим образом:
1️⃣ При освобождении блока помечаем его как свободный
2️⃣ Пытаемся объединить с соседними свободными
3️⃣ Если получилось, то переносим получившийся (более большой) блок в нужный FLI/SLI
А что такое соседние свободные блоки? Тут под ними понимаются не блоки, которые лежат рядом в корзинке! Совсем нет!
У каждого блока есть заголовок - это какая-то служебная (meta) информация. Часто там хранится размер блока, всякие флаги, индексы, и, указатели на соседние блоки. Когда мы делим большой блок на несколько маленьких (откусываем кусочек), то мы создаем двусвязный список, чтобы потом можно было этот кусочек вернуть обратно в большой пирог.
А что такое двусвязный список? Это ссылки на соседей "слева" и "справа".
Поэтому алгоритм упаковки достаточно простой:
1️⃣ При освобождении блока помечаем его как свободный
2️⃣ Смотрим на соседей, вдруг они тоже свободные
3️⃣ Если свободные, то объединяем в более большой блок
4️⃣ Удаляем объединенные блоки из корзин FLI/SLI, в которых они хранились
5️⃣ Сохраняем новый большой блок в новую корзину FLI/SLI
Вуаля, упаковка произошла, дырок больше нет!
Весь алгоритм выполняется за O(1): проверка на свободный блок - через операции с битмаской, поиск соседей - через двусвязный список, операции удаления/вставки - опять же немного пошаманить с битмаской и сохранить в нужную корзину.
Когда лучше всего использовать TLSF-аллокатор?
🔘 Нужны блоки памяти достаточно большого размера с длинным жизненным циклом (всякие долгоживущие массивы, которые хранятся в памяти, например, все время жизни игры)
🔘 Блоки разного размера
🔘 Нужна поддержка возврата и переиспользования (обычная арена не подходит)
В следующих постах расскажу о своей реализации аллокатора для вокселей: какой, почему именно такой, как я решила проблему с многопоточностью (та еще задачка
P.S.: мем от ChatGPT прилагается
#csharp #allocators
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥18🦄3❤🔥2🍓2🤩1
Всем привет! 💃
Продолжим тему с аллокаторами, ведь я о них не просто так рассказывала?🫥 Как я писала в одном из постов - я переосмысляю подходы в своей генерации. Причина этому является то, что я уперлась в некоторые ограничения при масштабировании, ну и просто подросла 😛
Что я поняла, так это то, что хранить воксели и их мета-данные очень и очень важно, особенно учитывая тот момент, что их много, а алгоритмы часто работают со всеми (или почти всеми) вокселями в чанках.🧂
И я пришла к мысли, что мне нужен свой аллокатор, который должен удовлетворять следующим требованиям:
1️⃣ Максимально быстрая аллокация непрерывных блоков памяти.
2️⃣ Минимум фрагментации памяти.
3️⃣ Максимум переиспользования памяти.
Я несколько раз пыталась аллоцировать сразу большой кусок памяти и его разрезать по чанкам и вокселям и как-то использовать. Это оказалось довольно медленно, так как часто память нужна уже "чистая", а аллоцирования и трогание (например очистка) памяти ведет к ее реальному выделению, а не только виртуальному бронированию.
С другой стороны, у меня алгоритмы не используют весь выделенный кусок сразу в один момент времени. Тут больше происходят действия вида "откусили кусочек", что-то поделали, часто даже 1-2 кадра, и отдали обратно. И только какая-то небольшая часть чанков и их память реально живет длительное время.
Из-за такого вида работы с памятью, мне не подходят аллокаторы Temp или TempJob. Они слишком мало живут. С другой стороны, Persistent аллокатор тоже не подходит, он хоть и живет долго, но он достаточно медленный и не избавляет от проблемы, что мне выдадут виртуальный кусок памяти, который на самом деле еще надо инициализировать, что ведет к неприятным задержкам.
Вариант, когда в Jobs мы используем Temp аллокатор, а результат вычислений копируем в Persistent память я тоже в итоге откинула. Это требует выделения памяти в большем количестве и добавляет копирование в конце. Не хочется делать те операции, от которых мы на самом деле можем избавиться.😔
Что же делать?🚪
Написать свой аллокатор и радоваться жизни!🙌
Я пришла к гибридному варианту, когда простую идею о переиспользовании уже выделенных кусков памяти (то есть обычный pooling) натягивают на сегрегацию по размеру блоков и оборачивают это все в lock-free алгоритм для поддержки многопоточного доступа.
Я не выделяю сразу большого куска памяти (хотя какую-то инициализацию на старте игры стоит делать), так как это сложно управляется в многопоточном коде. Быстрый доступ без блокировок - та еще задачка.🦊
Для аллокации шаги будут такими:
🔘 смотрим на размер запрошенного блока и находим пул, который больше всего подходит под него
🔘 смотрим, есть ли в пуле свободные блоки
🔘 если есть, то берем свободный блок, и откусываем от него (если он слишком большой для нас)
🔘 если нет, то запрашиваем у back-аллокатора (в моем случае это Persistent) новый кусок
🔘 полученный кусок возвращаем пользователю
Для деаллокации шаги похожи:
🔘 смотрим на размер блока и вычисляем пул, из которого этот блок был взят
🔘 кладем в пул и помечаем блок как свободный
Более подробно о том, как я реализовала свой аллокатор и как его подключила к Unity расскажу в следующих постах!💃
И конечно же для него я написала Profiler Module с подробной статистикой. Спасибо, что подсказали про это!🥰
#generation #allocators
Продолжим тему с аллокаторами, ведь я о них не просто так рассказывала?
Что я поняла, так это то, что хранить воксели и их мета-данные очень и очень важно, особенно учитывая тот момент, что их много, а алгоритмы часто работают со всеми (или почти всеми) вокселями в чанках.
И я пришла к мысли, что мне нужен свой аллокатор, который должен удовлетворять следующим требованиям:
1️⃣ Максимально быстрая аллокация непрерывных блоков памяти.
2️⃣ Минимум фрагментации памяти.
3️⃣ Максимум переиспользования памяти.
Я несколько раз пыталась аллоцировать сразу большой кусок памяти и его разрезать по чанкам и вокселям и как-то использовать. Это оказалось довольно медленно, так как часто память нужна уже "чистая", а аллоцирования и трогание (например очистка) памяти ведет к ее реальному выделению, а не только виртуальному бронированию.
С другой стороны, у меня алгоритмы не используют весь выделенный кусок сразу в один момент времени. Тут больше происходят действия вида "откусили кусочек", что-то поделали, часто даже 1-2 кадра, и отдали обратно. И только какая-то небольшая часть чанков и их память реально живет длительное время.
Из-за такого вида работы с памятью, мне не подходят аллокаторы Temp или TempJob. Они слишком мало живут. С другой стороны, Persistent аллокатор тоже не подходит, он хоть и живет долго, но он достаточно медленный и не избавляет от проблемы, что мне выдадут виртуальный кусок памяти, который на самом деле еще надо инициализировать, что ведет к неприятным задержкам.
Вариант, когда в Jobs мы используем Temp аллокатор, а результат вычислений копируем в Persistent память я тоже в итоге откинула. Это требует выделения памяти в большем количестве и добавляет копирование в конце. Не хочется делать те операции, от которых мы на самом деле можем избавиться.
Что же делать?
Написать свой аллокатор и радоваться жизни!
Я пришла к гибридному варианту, когда простую идею о переиспользовании уже выделенных кусков памяти (то есть обычный pooling) натягивают на сегрегацию по размеру блоков и оборачивают это все в lock-free алгоритм для поддержки многопоточного доступа.
Я не выделяю сразу большого куска памяти (хотя какую-то инициализацию на старте игры стоит делать), так как это сложно управляется в многопоточном коде. Быстрый доступ без блокировок - та еще задачка.
Для аллокации шаги будут такими:
🔘 смотрим на размер запрошенного блока и находим пул, который больше всего подходит под него
🔘 смотрим, есть ли в пуле свободные блоки
🔘 если есть, то берем свободный блок, и откусываем от него (если он слишком большой для нас)
🔘 если нет, то запрашиваем у back-аллокатора (в моем случае это Persistent) новый кусок
🔘 полученный кусок возвращаем пользователю
Для деаллокации шаги похожи:
🔘 смотрим на размер блока и вычисляем пул, из которого этот блок был взят
🔘 кладем в пул и помечаем блок как свободный
Более подробно о том, как я реализовала свой аллокатор и как его подключила к Unity расскажу в следующих постах!
И конечно же для него я написала Profiler Module с подробной статистикой. Спасибо, что подсказали про это!
#generation #allocators
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥25❤3😨3🍾1
SDF Functions
В поисках всякого про SDF и не только, наткнулась на неплохой сайт, где есть описание ооочень большого количества 3D SDF функций.🗣
Выглядит прикольно, о некоторых я даже не подозревала, так как обычно сложные формы запекаются.
Хотите бублик?
Или может Звезду Смерти (Дарт Вейдер одобряет)?
Там много интересного, в том числе и как производить всякие манипуляции с SDF-фигурами.💃
Также ко многим примерам есть ссылочки на Shader Toy, где можно в динамике посмотреть фигуры. Крутотень!🙌
#sdf #generation
В поисках всякого про SDF и не только, наткнулась на неплохой сайт, где есть описание ооочень большого количества 3D SDF функций.
Выглядит прикольно, о некоторых я даже не подозревала, так как обычно сложные формы запекаются.
Хотите бублик?
float sdTorus( vec3 p, vec2 t )
{
vec2 q = vec2(length(p.xz)-t.x,p.y);
return length(q)-t.y;
}
Или может Звезду Смерти (Дарт Вейдер одобряет)?
float sdDeathStar( vec3 p2, float ra, float rb, float d )
{
// sampling independent computations (only depend on shape)
float a = (ra*ra - rb*rb + d*d)/(2.0*d);
float b = sqrt(max(ra*ra-a*a,0.0));
// sampling dependant computations
vec2 p = vec2( p2.x, length(p2.yz) );
if( p.x*b-p.y*a > d*max(b-p.y,0.0) )
return length(p-vec2(a,b));
else
return max( (length(p )-ra),
-(length(p-vec2(d,0.0))-rb));
}
Там много интересного, в том числе и как производить всякие манипуляции с SDF-фигурами.
Также ко многим примерам есть ссылочки на Shader Toy, где можно в динамике посмотреть фигуры. Крутотень!
#sdf #generation
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17🍌5🎃4
Всем привет! 💃
Продолжим тему с аллокатором, который я реализовала для хранения различных данных вокселей. Как я уже рассказывала в предыдущем посте, я использую достаточно простой подход - обычный пуллинг памяти.
Как же устроен мой пул?📝
1️⃣ Я выделяю заранее буфер на много-много указателей блоков. Да, я храню не сами блоки, я храню только указатели (8 байт). Сколько таких блоков может быть в пуле - подобрано эвристически в зависимости от моих задач. Размер этого буфера не меняется.
2️⃣ Я выделяю также и общий список Next Free. Этот список для каждого элемента хранит индекс следующего свободного. Получается односвязный список, по которому я могу найти за O(1) следующий свободный элемент. Скорость мне очень важна.
3️⃣ Я выделяю Thread Local Storage. Это необходимо, чтобы было как можно меньше пересечений между потоками и кэш-миссов из-за инвалидации кэша + меньше блокировок. В TLS хранится следующий свободный индекс для каждого потока.
Как же я ищу блок в пуле?🚪
Давайте представим, что мы находимся в каком потоке.
1️⃣ Я смотрю в Thread Local Storage по индексу этого потока. Вдруг там есть свободный индекс, которые мы можем использовать.
2️⃣ Если не нашла, то я пытаюсь забрать свободные индексы, которые пока не принадлежат ни одному потоку. Дело в том, что я не разбиваю сразу все индексы из пула по всем потокам, я их "отдаю" пачками, по 16 штук при необходимости.
3️⃣ Получив индекс так или иначе, я обновляю связаный список Next Free и TLS. Это просто установка значения, что занимает снова O(1).
4️⃣ По полученному индексу я получаю указатель блока из пула, если он был раннее аллоцирован. Если же нет, то аллоцирую новый блок по указанному back-аллокатору (чаще всего, это Persistent).
Пример как можно найти доступный индекс:
Пример как можно "отдать" еще 16 индексов потоку:
А что если мы использовали все, что было в пуле, и что места под блоки? Тогда для потока я не смогу найти еще 16 индексов.
Получается, что мы израсходовали все, что у нас было. Но, вдруг, у других потоков еще есть свободные индексы?
Попробуем украсть их!
Алгоритм кражи тоже простой:
1️⃣ Пробегаемся по всем TLS всех потоков, кроме нашего текущего.
2️⃣ Смотрим, есть ли у них что-то, что можно украсть.
3️⃣ Если есть, то крадем. В TLS потока, у которого крадем, помечаем, что мы забрали индекс.
Пример кражи:
Спасибо за внимание!💃
#generation #allocators
Продолжим тему с аллокатором, который я реализовала для хранения различных данных вокселей. Как я уже рассказывала в предыдущем посте, я использую достаточно простой подход - обычный пуллинг памяти.
Как же устроен мой пул?
1️⃣ Я выделяю заранее буфер на много-много указателей блоков. Да, я храню не сами блоки, я храню только указатели (8 байт). Сколько таких блоков может быть в пуле - подобрано эвристически в зависимости от моих задач. Размер этого буфера не меняется.
2️⃣ Я выделяю также и общий список Next Free. Этот список для каждого элемента хранит индекс следующего свободного. Получается односвязный список, по которому я могу найти за O(1) следующий свободный элемент. Скорость мне очень важна.
3️⃣ Я выделяю Thread Local Storage. Это необходимо, чтобы было как можно меньше пересечений между потоками и кэш-миссов из-за инвалидации кэша + меньше блокировок. В TLS хранится следующий свободный индекс для каждого потока.
Как же я ищу блок в пуле?
Давайте представим, что мы находимся в каком потоке.
1️⃣ Я смотрю в Thread Local Storage по индексу этого потока. Вдруг там есть свободный индекс, которые мы можем использовать.
2️⃣ Если не нашла, то я пытаюсь забрать свободные индексы, которые пока не принадлежат ни одному потоку. Дело в том, что я не разбиваю сразу все индексы из пула по всем потокам, я их "отдаю" пачками, по 16 штук при необходимости.
3️⃣ Получив индекс так или иначе, я обновляю связаный список Next Free и TLS. Это просто установка значения, что занимает снова O(1).
4️⃣ По полученному индексу я получаю указатель блока из пула, если он был раннее аллоцирован. Если же нет, то аллоцирую новый блок по указанному back-аллокатору (чаще всего, это Persistent).
Пример как можно найти доступный индекс:
do
{
do
{
// читаем из TLS текущего потока
idx = Volatile.Read(ref TLS[threadIndex]);
}while (idx == -3);
if(idx < 0)
// в TLS нашего потока больше нет ничего
}while (Interlocked.CompareExchange(ref TLS[threadIndex], -3, idx) != idx);
// записали следующий свободный в наш TLS
Interlocked.Exchange(ref TLS[threadIndex], nextPtrs[idx]);
Пример как можно "отдать" еще 16 индексов потоку:
Interlocked.Exchange(ref poolData->FirstFreeTLS[threadIndex], -2); // помечаем, что мы будем еще выделять
if (allocatedCount < capacity)
{
idx = Interlocked.Add(ref allocatedCount, 16) - 16; // пытаемся отдать все 16 индексов
if (idx < capacity - 1)
{
var count = math.min(16, capacity - idx);
for (var i = 1; i < count; ++i)
{
// сразу их записываем в список свободных
nextPtrs[idx + i] = idx + i + 1;
}
nextPtrs[idx + count - 1] = -1;
nextPtrs[idx] = -1; // текущий мы отдаем, поэтому он не свободен
// и в TLS тоже записываем следующий свободный
Interlocked.Exchange(ref TLS[threadIndex], idx + 1);
return idx;
}
if (idx == capacity - 1)
{
// это был последний доступный в пуле, значит помечаем как "больше элементов нет"
Interlocked.Exchange(ref TLS[threadIndex], -1);
return idx;
}
}
// ничего не нашли, следующих свободных нет
Interlocked.Exchange(ref TLS[threadIndex], -1);
А что если мы использовали все, что было в пуле, и что места под блоки? Тогда для потока я не смогу найти еще 16 индексов.
Получается, что мы израсходовали все, что у нас было. Но, вдруг, у других потоков еще есть свободные индексы?
Попробуем украсть их!
Алгоритм кражи тоже простой:
1️⃣ Пробегаемся по всем TLS всех потоков, кроме нашего текущего.
2️⃣ Смотрим, есть ли у них что-то, что можно украсть.
3️⃣ Если есть, то крадем. В TLS потока, у которого крадем, помечаем, что мы забрали индекс.
Пример кражи:
for(var threadIndex = 0; threadIndex != currentIndex; threadIndex < threadCount; threadIndex++)
{
do
{
do
{
idx = Volatile.Read(ref TLS[threadIndex]);
}while(idx == -3);
if(idx < 0)
break;
}while (Interlocked.CompareExchange(ref TLS[threadIndex], -3, idx) != idx);
}
Спасибо за внимание!
#generation #allocators
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍3❤2
Всем привет! 👋
В прошлых постах я как-то показала как выглядит мой модуль Profiler для моего аллокатора. Да и в комментариях советовали написать свой модуль под свои запросы. Это был отличный совет, потому что трекать по конкретным метрикам, описанным заранее, нааамного удобнее!🥹
Вот тут у Unity есть документация, как сделать такие красивости самостоятельно, но как обычно это бывает, там нет всей информации.
Я не нашла как трекать все свои метрики, в особенности, как трекать большие объемы данных (массивы данных) и как их потом получить в самом модуле для отрисовки.😔
Чтобы записать свои данные, которые потом можно будет получать покадрово, надо воспользоваться счетчиками ProfilerCounter:
Для больших объемов, когда необходимо засэмплить целый буфер, можно воспользоваться другим API - Profiler.EmitFrameMetaData. Туда можно передать как обычный массив/список, так и нативный массив. Разве что нативный NativeArray необходимо создавать с аллокатором Temp. Видимо внутри они просто копируют все переданные данные в свой стрим, откуда потом читают.
А вот чтобы прочитать и отрисовать потом данные по какому-то кадру, стоит воспользоваться ProfilerDriver.GetRawFrameDataView.
В этот метод передается номер кадра (в Profiler его можно получить через ProfilerWindow.SelectedFrameIndexChanged) и индекс потока (0 для main thread).
Полученный объект RawFrameDataView позволяет уже прочитать данные по тем же самым айдишникам, что были указаны в счетчиках раннее. В целом удобно.😛
Спасибо за внимание!🥰
#unity
В прошлых постах я как-то показала как выглядит мой модуль Profiler для моего аллокатора. Да и в комментариях советовали написать свой модуль под свои запросы. Это был отличный совет, потому что трекать по конкретным метрикам, описанным заранее, нааамного удобнее!
Вот тут у Unity есть документация, как сделать такие красивости самостоятельно, но как обычно это бывает, там нет всей информации.
Я не нашла как трекать все свои метрики, в особенности, как трекать большие объемы данных (массивы данных) и как их потом получить в самом модуле для отрисовки.
Чтобы записать свои данные, которые потом можно будет получать покадрово, надо воспользоваться счетчиками ProfilerCounter:
public static readonly ProfilerCounter<long> TotalAllocatedMemoryCounter = new(ProfilerCategory,
TotalAllocatedMemoryName, ProfilerMarkerDataUnit.Bytes);
...
// и вот так записывать значение каждый кадр
TotalAllocatedMemoryCounter.Sample(frameData.TotalAllocatedMemory);
Для больших объемов, когда необходимо засэмплить целый буфер, можно воспользоваться другим API - Profiler.EmitFrameMetaData. Туда можно передать как обычный массив/список, так и нативный массив. Разве что нативный NativeArray необходимо создавать с аллокатором Temp. Видимо внутри они просто копируют все переданные данные в свой стрим, откуда потом читают.
А вот чтобы прочитать и отрисовать потом данные по какому-то кадру, стоит воспользоваться ProfilerDriver.GetRawFrameDataView.
В этот метод передается номер кадра (в Profiler его можно получить через ProfilerWindow.SelectedFrameIndexChanged) и индекс потока (0 для main thread).
Полученный объект RawFrameDataView позволяет уже прочитать данные по тем же самым айдишникам, что были указаны в счетчиках раннее. В целом удобно.
Спасибо за внимание!
#unity
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥16❤12🥰5
Всем привет! 💃
Вот и настал последний понедельник года, это время не только подвести итоги, но и пожелать друг другу хорошего в новом году!🥔
Многие из вас растут как эксперты, у некоторых из вас есть свои пет-проекты, а кто-то может даже начинает игру с мыслями про будущие успехи.
Пусть 2026 год поможет довести начатое до результата, воплотит все ваши мечты и приведет вас к вершинам, о которых вы даже не задумываетесь сейчас!🤑
Пусть следующий год будет годом роста, смелых экспериментов и новых достижений!🙌
Спасибо вам всем, что читаете мой канал и терпеливо ждете новые посты.❤️
Счастливого Нового Года!🙌
Вот и настал последний понедельник года, это время не только подвести итоги, но и пожелать друг другу хорошего в новом году!
Многие из вас растут как эксперты, у некоторых из вас есть свои пет-проекты, а кто-то может даже начинает игру с мыслями про будущие успехи.
Пусть 2026 год поможет довести начатое до результата, воплотит все ваши мечты и приведет вас к вершинам, о которых вы даже не задумываетесь сейчас!
Пусть следующий год будет годом роста, смелых экспериментов и новых достижений!
Спасибо вам всем, что читаете мой канал и терпеливо ждете новые посты.
Счастливого Нового Года!
Please open Telegram to view this post
VIEW IN TELEGRAM
2🎄42❤13🔥5🆒1
Всем привет! 💃
Ох, давно я не писала чисто Entities-код, все джобы, аллокаторы и всякое остальное не очень интересное.
Недавно продолжила работу над проектом (новогодние праздники, плюс под конец декабря уволилась из компании, в которой работала, но это отдельная история), обновила версию Unity, пакетов, так как все же прошло много времени и много багов со стороны Unity было пофикшено.
Но столкнулась с новым для себя багом: в случае кастомного бутстрапа мира Entities + ручной загрузки ECS-сцен, можно в билде получить бесячую ошибку "Cannot find TypeIndex for type hash"🤩
После часов поиска причины, нашла топик на форуме, в котором описывается, что ILPP в Entities 1.3.9+ сломан.
Текущий workaround - указать DISABLE_TYPEMANAGER_ILPP в настройках проекта, тогда все будет работать отлично.
Может быть кому-нибудь будет полезно❤️
А как проходят ваши новогодние праздники? 😅
Ох, давно я не писала чисто Entities-код, все джобы, аллокаторы и всякое остальное не очень интересное.
Недавно продолжила работу над проектом (новогодние праздники, плюс под конец декабря уволилась из компании, в которой работала, но это отдельная история), обновила версию Unity, пакетов, так как все же прошло много времени и много багов со стороны Unity было пофикшено.
Но столкнулась с новым для себя багом: в случае кастомного бутстрапа мира Entities + ручной загрузки ECS-сцен, можно в билде получить бесячую ошибку "Cannot find TypeIndex for type hash"
После часов поиска причины, нашла топик на форуме, в котором описывается, что ILPP в Entities 1.3.9+ сломан.
Текущий workaround - указать DISABLE_TYPEMANAGER_ILPP в настройках проекта, тогда все будет работать отлично.
Может быть кому-нибудь будет полезно
А как проходят ваши новогодние праздники? 😅
Please open Telegram to view this post
VIEW IN TELEGRAM
❤18🥴1