Эффективно избавляться от мертвых объектов в игре?

10

Я использую цикл for или цикл foreach (не имеет значения), чтобы перебрать все мои объекты, когда их нужно обновить или нарисовать. Однако, когда объект убивают, я хочу, чтобы он снова был удален из коллекции.

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

Есть ли лучший способ сделать это?

Матиас Ликкегор Лоренцен
источник
Соответствующий: Двойные пути к нирване сборщика мусора
doppelgreener
2
Это действительно не имеет ничего общего со сборкой мусора, несмотря на использование слова «мусор» в заголовке и ответов, которые упоминали сборщик мусора .NET.
Эндрю Рассел
Я позволил себе сменить слово «мусор» на «мертвый», чтобы избежать путаницы с сборщиком мусора .NET.
Nevermind
О, я знаю об этом. Статья по сборке мусора по-прежнему актуальна, потому что она может сыграть роль в том, что вы делаете с этими мертвыми объектами. Например, мертвый мусор? Отсоедините его от всего, сбросьте его до значений по умолчанию, поместите обратно в корзину для мусора.
двойник

Ответы:

11

Прежде всего: терминология. Если вы скажете «список мусора», люди подумают, что вы говорите о сборщике мусора. Давайте назовем это «мертвым списком».

Как вы, вероятно, обнаружили, отсюда и ваш вопрос: вы не можете удалять элементы из коллекции, пока выполняете ее. Если ваша коллекция - List<>то самый простой способ удалить элементы из нее:

for(int i = list.Count-1; i >= 0; --i)
{
    if(list[i].IsDead)
        list.RemoveAt(i);
}

Обратите внимание, что вы повторяете в обратном направлении . Вы можете удалять элементы во время итерации вперед, но это сложнее и требует goto. Вы не можете сделать это с foreach.

Вы можете сделать удаление отдельно от цикла обновления. Или вы можете объединить его в свой цикл обновления. Всегда делайте RemoveAtв конце цикла - после этой точки в цикле,list[i] нельзя считать действительным (оно снова становится действительным на следующей итерации).

Также обратите внимание, что каждый объект имеет свой собственный IsDeadфлаг (если вы используете шаблон удаления, вы можете сделать его IsDisposed) - вам вообще не нужно поддерживать «мертвый список».

Использование флага для каждого элемента предпочтительнее для производительности, так как вам не нужно искать в списке, чтобы выполнить удаление. И это также предпочтительно для дизайна - это означает, что каждый объект может легко проверить, мертв ли ​​он, - так что вы случайно не вызываете какие-либо методы для него (если эти объекты реализуют одноразовый шаблон, они могут бросить ObjectDisposedExceptionв этом случае).

Если у вас есть коллекция, отличная от a List<>, то удаление мертвых элементов из этой коллекции может по-прежнему включать создание мертвого списка (поскольку удаление во время итерации может быть невозможным). На мой взгляд, лучше создавать дизайн мертвого списка непосредственно перед его использованием, перебирая коллекцию в поисках IsDeadфлагов, вместо того, чтобы пытаться поддерживать список при уничтожении объектов.

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

Эндрю Рассел
источник
Таким образом, одноразовая модель считается более «эффективной», чем?
Джонатан Коннелл
@ Джонатан: я не понимаю твой вопрос. IsDeadКартина функционально идентичны IsDisposed. Семантическое использование IsDeadподразумевает, что вы ведете список Draw / Update этих объектов, из которого будут удалены мертвые элементы в будущем. Схема утилизации не подразумевает этого. Кроме того, схема утилизации действительно означает , что вы можете иметь неуправляемые ресурсы , принадлежащие этот объект (который, как правило , не подходит для объектов игрового процесса).
Эндрю Рассел
Вы можете сделать это с помощью foreach, используя реверс.
Shinzou
0

В 99,9% случаев сборщик мусора (GC) позаботится обо всем без проблем. Просто дайте ему сделать свою работу, как только вы удалите / обнуляете эти объекты. Существует задержка в очистке, которая зависит от ряда факторов (например, сколько блоков памяти было выделено), но вам обычно не нужно даже знать об этом, не говоря уже о том, чтобы беспокоиться об этом. Он предназначен только для работы. По какой причине вы используете C #, а не C.

Относительно производительности GC в целом, не беспокойтесь слишком сильно, если только вы не знаете (через профилирование), что это создает узкое место - это только в довольно требовательных играх, как правило. Если это так, посмотрите на GC.Collect () и его различные подводные камни, чтобы вы знали, когда это целесообразно использовать. Я предлагаю погуглить "force gc xna", там много материала.

инженер
источник
Так что, если у меня есть список, который я использую в своем вызове Update () каждый раз, все элементы в нем, где элемент имеет значение «ноль», будут автоматически очищены и удалены?
Матиас Ликкегор Лоренцен
@Mathias Нет. Смотрите мой комментарий на ваш вопрос. Сборщик мусора не изменит ничего, к чему вы все еще можете получить доступ, включая вашу коллекцию и ее содержимое.
Эндрю Рассел
1
Вопрос не имеет ничего общего с GC, кроме неудачного выбора слова «мусор».
Nevermind