UNSAFECSHARP Telegram 215
UnityEngine.Object == null

Мне не так давно написал один из разработчиков на проекте, который застрял на проблеме зомби объектов.

Зомби объект — жив потому что GC не может его собрать, так как в стэке есть ссылка на него, но сам объект уже Destroyed.

И дело тут не в том, что GC.Collect еще не вызвался, а в том что мы храним указатель на объект, который уже уничтожен.

Такую ситуацию очень легко получить:
🔹Берем любой класс наследник MonoBehaviour
🔹 Реализуем в нем любой интерфейс
🔹 Instantiate'им объект, сохраняем в переменную
🔹 Во вторую переменную cast'им наш объект к интерфейсу
🔹 Уничтожаем MonoBehaviour
Через Object.Destroy или Object.DestroyImmediate
🔹 Пытаемся вызвать поле или метод интерфейса

Вопросы:
🔸 Будет ли MonoBehaviour == null?
🔸 Будет ли переменная интерфейса == null?
🔸 Будет ли ошибка при вызове любого member'а интерфейса?

Подсказка

Ответы:
👍 Да
👎 Нет
И да и нет.
Если вызваемый мембер не часть MonoBehavior имплементации - ошибки не будет
В остальных случаях будет MissingRefere
nceException

Почему так?
Потому что любой UnityEngine.Object имеет 2 runtime части:
🔹Одна на стороне unity (написанная на C++)
🔹Вторая на стороне CLR (Common Language Runtime) — класс/структура C#

Это значит:
🔸 CLRObject может смотреть уже на уничтоженный UnityObject ровно столько, сколько мы храним указатель на него
🔸 Если UnityObject уничтожен, мы все еще можем обратиться к его CLR части и взять оттуда любые данные, которые не является частью UnityObject
🔸 Момент сборки данного объекта GC может быть отложен на сколько угодно по времени

Это ведет к проблемам:
♦️ Утечки памяти по всему проекту.
UnityObject уничтожен и нам нужно удалить объект из коллекции, а мы не можем, потому что interface != null
♦️MissingReferenceException, в неожиданных местах со вкусом фрустрации и сложной отладки

4 возможных решения:
1️⃣ Проверять все экземпляры типа интерфейса методом:

bool IsNullUniversal<T>(T instance)
{
  if (instance is Object unityObject)
    return unityObject == null;

  return instance == null;
}

2️⃣ Для абстракции ВСЕХ монобехов использовать только abstract классы
3️⃣ Наследовать все интерфейсы от IEquatable<T> и использовать везде метод Equals вместо ==
4️⃣ (самый быстрый, самый дерзкий)
Через UnsafeUtility читать m_CachedPtr и m_InstanceID и через рефлексию дергать метод DoesObjectWithInstanceIDExist

Почему именно так:
🔸UnityObject содержит перегрузку оператора == и != которая проверят что нативная (C++ часть) "жива"
🔻Но в CLR части == транслируется в операцию seq, которая в после IL2CPP будет выглядеть так:
((((RuntimeObject*)instance) == ((RuntimeObject*)NULL))? 1 : 0)

Или проще говоря в обычное сравнение 2ух указателей.

Такой проверки в случае реализации интерфейса в UnityObject не достаточно.
Потому мы получается false-negative результат при сравнение на null, который потенциально ведет к утечкам памяти

Я создал Gist в котором расписал подробно TestCase'ы и решение.
Не стесняйтесь его дополнить или предложить свой вариант в комментариях 😎

Об архитектуре проектов на unity я пишу в своем канале, подписывайся❤️‍🔥

Специально для @unsafecsharp
🔥55👍16👏3🤔1



tgoop.com/unsafecsharp/215
Create:
Last Update:

UnityEngine.Object == null

Мне не так давно написал один из разработчиков на проекте, который застрял на проблеме зомби объектов.

Зомби объект — жив потому что GC не может его собрать, так как в стэке есть ссылка на него, но сам объект уже Destroyed.

И дело тут не в том, что GC.Collect еще не вызвался, а в том что мы храним указатель на объект, который уже уничтожен.

Такую ситуацию очень легко получить:
🔹Берем любой класс наследник MonoBehaviour
🔹 Реализуем в нем любой интерфейс
🔹 Instantiate'им объект, сохраняем в переменную
🔹 Во вторую переменную cast'им наш объект к интерфейсу
🔹 Уничтожаем MonoBehaviour
Через Object.Destroy или Object.DestroyImmediate
🔹 Пытаемся вызвать поле или метод интерфейса

Вопросы:
🔸 Будет ли MonoBehaviour == null?
🔸 Будет ли переменная интерфейса == null?
🔸 Будет ли ошибка при вызове любого member'а интерфейса?

Подсказка

Ответы:
👍 Да
👎 Нет
И да и нет.
Если вызваемый мембер не часть MonoBehavior имплементации - ошибки не будет
В остальных случаях будет MissingRefere
nceException

Почему так?
Потому что любой UnityEngine.Object имеет 2 runtime части:
🔹Одна на стороне unity (написанная на C++)
🔹Вторая на стороне CLR (Common Language Runtime) — класс/структура C#

Это значит:
🔸 CLRObject может смотреть уже на уничтоженный UnityObject ровно столько, сколько мы храним указатель на него
🔸 Если UnityObject уничтожен, мы все еще можем обратиться к его CLR части и взять оттуда любые данные, которые не является частью UnityObject
🔸 Момент сборки данного объекта GC может быть отложен на сколько угодно по времени

Это ведет к проблемам:
♦️ Утечки памяти по всему проекту.
UnityObject уничтожен и нам нужно удалить объект из коллекции, а мы не можем, потому что interface != null
♦️MissingReferenceException, в неожиданных местах со вкусом фрустрации и сложной отладки

4 возможных решения:
1️⃣ Проверять все экземпляры типа интерфейса методом:


bool IsNullUniversal<T>(T instance)
{
  if (instance is Object unityObject)
    return unityObject == null;

  return instance == null;
}

2️⃣ Для абстракции ВСЕХ монобехов использовать только abstract классы
3️⃣ Наследовать все интерфейсы от IEquatable<T> и использовать везде метод Equals вместо ==
4️⃣ (самый быстрый, самый дерзкий)
Через UnsafeUtility читать m_CachedPtr и m_InstanceID и через рефлексию дергать метод DoesObjectWithInstanceIDExist

Почему именно так:
🔸UnityObject содержит перегрузку оператора == и != которая проверят что нативная (C++ часть) "жива"
🔻Но в CLR части == транслируется в операцию seq, которая в после IL2CPP будет выглядеть так:
((((RuntimeObject*)instance) == ((RuntimeObject*)NULL))? 1 : 0)

Или проще говоря в обычное сравнение 2ух указателей.

Такой проверки в случае реализации интерфейса в UnityObject не достаточно.
Потому мы получается false-negative результат при сравнение на null, который потенциально ведет к утечкам памяти

Я создал Gist в котором расписал подробно TestCase'ы и решение.
Не стесняйтесь его дополнить или предложить свой вариант в комментариях 😎

Об архитектуре проектов на unity я пишу в своем канале, подписывайся❤️‍🔥

Специально для @unsafecsharp

BY Unity: Всё, что вы не знали о разработке


Share with your friend now:
tgoop.com/unsafecsharp/215

View MORE
Open in Telegram


Telegram News

Date: |

The Standard Channel Polls The administrator of a telegram group, "Suck Channel," was sentenced to six years and six months in prison for seven counts of incitement yesterday. Informative How to Create a Private or Public Channel on Telegram?
from us


Telegram Unity: Всё, что вы не знали о разработке
FROM American