Рендеринг с использованием слабых ссылок и GC

Эта проблема

Я недавно начал учиться C#, Я делаю это через создание игры (так как я довольно знаком с этим в C++).

Объекты, которые должны быть выведены в задний буфер, "регистрируются" при создании через событие, отправляемое со слабой ссылкой на объект. С C++ умные указатели подсчитывают ссылки и (в результате) объекты уничтожаются, как только последняя ссылка выходит из области видимости, это хорошо работает для моих целей. Однако в C# это, конечно, не тот случай.

Рассмотрим следующий фрагмент кода:

foreach(WeakReference<DrawableObject> drawable in drawables.ToList())
{
    DrawableObject target;
    drawable.TryGetTarget(out target);
    if(target != null)
    {
        spriteBatch.Draw(target.Texture, target.Position, target.Rectangle, target.Colour);
    }
    else
    {
        drawables.Remove(drawable);
    }
}

Основная проблема (как я уверен, есть, скорее всего, другие, которые я еще не понял) с этим в C#, в том, что target не гарантируется null после того, как последняя сильная ссылка на объект выходит из области видимости.


Потенциальные решения

Принудительный сбор мусора

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

Использование метода Hide()

Хотя такой метод необходим для любой игры, необходимо явно Hide() каждый нарисованный объект утомителен и дает больше места для ошибок. Кроме того, когда все эти Hide() будет называться? Я также не могу рассчитывать на то, что родительский объект будет собран вовремя, поэтому помещаю их в Finalizer не решит проблему.

Реализация IDisposable

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


Вопросы)

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

  • Это подходящий вариант использования для принудительного сбора мусора?

  • Если нет, как я могу определить, должен ли объект быть собран при следующем запуске GC?

  • Есть ли способ вызвать метод объекта, как только последняя ссылка на него выходит из области видимости (неявно или явно), вместо того, чтобы ждать появления GC?

Я, конечно, благодарен за любые предложения, которые также позволят избежать этой проблемы.

1 ответ

Решение

Я собираюсь попытаться ответить на это..

То, что вы ищете, это логика "Я мертв по правилам игры". Не "Этот объект мертв, потому что Сборщик мусора так говорит", логика.

Для этого... вам нужно переключить свои мысли на что-то вроде этого:

class DrawableObject {
    public bool IsDead { get; set; }
    public int Health { get; set; }

    // ...
    public void GetShot(int amount) {
        Health -= amount;

        if (Health <= 0)
            IsDead = true;
    }
}

..затем:

foreach (var drawable in drawables) {

    DrawableObject target;
    drawable.TryGetTarget(out target);

    if(target == null || target.IsDead) {
        drawables.Remove(drawable);
    }
    else {
        spriteBatch.Draw(target.Texture, target.Position, target.Rectangle, target.Colour);
    }
}

Если это null основываясь на вашем TryGetXXX логика... или игровая логика помечена как мертвая... удалите ее из списка прорисовываемых объектов. Позвольте сборщику мусора убирать его всякий раз, когда он захочет после этого.

TLDR: помечайте свои объекты как мертвые и основывайте свою логику на собственном флаге "Я мертв".

Другие вопросы по тегам