Як мені найкраще видалити об'єкт із циклу гри, коли він мертвий?


16

Гаразд, у мене є великий список усіх моїх сутностей, які я переглядаю і оновлюю. У AS3 я можу зберігати це як масив (динамічна довжина, нетипізований), вектор (набраний) або пов'язаний список (не рідний). Наразі я використовую Array, але я планую перейти на Vector або пов'язаний список, якщо він швидше.

У будь-якому разі, моє запитання, коли організація знищена, як я можу видалити її зі списку? Я міг би зняти його положення, розклеїти його або просто встановити на ньому прапор, щоб сказати "пропусти через мене, я мертвий". Я об'єдную свої сутності, тому цілком можливо, що Єдине, яке померло, в якийсь момент знову оживе. Що стосується кожного типу колекції, яка моя найкраща стратегія, і яке поєднання типу збирання та методу видалення буде найкращим чином працювати?


Довжина вектора не фіксована, але вона набрана, і це робить її перевагою над масивом. Недолік - немає швидкого синтаксису для визначення попередньо заповненого списку, але вам це не потрібно, я думаю.
Барт ван Хекелом

Відповіді:


13

Я б зберігав усі додавання / видалення в окремих списках і виконував би ці операції після того, як я повторювався через цикл оновлення.


10

Рамка Flixel використовує мертвий прапор (насправді кілька прапорів, які визначають, чи слід його малювати, оновлювати тощо). Я б сказав, що якщо ви збираєтеся відроджувати об'єкти, і якщо продуктивність викликає проблеми, ви використовуєте мертвий прапор. На мій досвід, інстанціювання нових об'єктів - найдорожча операція у описаному вами випадку використання, а сплайсинг або обнулення елементів може призвести до роздуття пам'яті, якщо Flash-інколи збирає сміття.


1
+1 для флікселя. Переробка deadсправді допомагає у виконанні.
Снігові сліпи

3

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

Просто для вирішення деяких прийомів, про які вже говорили інші. Якщо порядок організацій важливий, то мертвий прапор може дозволити вам сплайсувати під час оновлення цикл на наступному кадрі. напр. дуже простий псевдокод:

void updateGame()
{
  // updateEntities()
  Entity* pSrcEntity = &mEntities[0];
  Entity* pDstEntity = &mEntities[0];
  newNumEntities = 0;
  for (int i = 0; i < numEntities; i++)
  {
    if (!pSrcEntity->isDead)
    {
       // could be inline but whatever.
       updateEntity(pDstEntity, pSrcEntity);
       // if entity just died, don't update the pDstEntity pointer, 
       // and just let the next entity updated overwrite it.
       if (!pDstEntity->isDead)
       {
          pDstEntity++;
          newNumEntities++;
       }
    }
    pSrcEntity++;
  }
}
numEntities = newNumEntities;

Це характеристики цієї схеми:

  • природна компактність об'єктів (хоча з можливою затримкою в одному кадрі до того, як слот сукупності може бути повернутий).
  • жодних випадкових запитів на повторне замовлення.
  • в той час як подвійно пов'язані списки мають O (1) вставлення / видалення, але їх дуже важко попередньо встановити для оптимального приховування затримки кешу. Зберігання їх у компактному масиві дозволяє приємно працювати з технологіями попереднього вибору блоків.
  • У разі знищення кількох об'єктів вам не потрібно робити зайві зміни-копії, щоб підтримувати порядок і компактність (все це робиться один раз під час проходу оновлення)
  • Ви скористаєтесь торканням даних, які потрібно буде знаходитись у кеші вже під час оновлення.
  • Це добре працює, якщо ваші джерела та пункти призначення об’єднати масиви для розділення масивів. Потім ви можете подвоїти масиви сутності, щоб скористатися перевагами багатоядерності / напр. один потік оновлення / запис сутностей для кадру N, а інший потік об'єктів попереднього кадру для кадру N-1.
  • Компактність означає, що простіше передати DMA весь пакет гетерогенному процесору для ще більшого завантаження роботи процесора, наприклад. SPU або GPU.

+1. Мені подобається це. Хоча мені навряд чи потрібні замовлені оновлення в басейні, я додаю його до сумки речей, щоб запам'ятати, якщо я зіткнувся з ситуацією: o)
Кей,

2

Якщо говорити з точки зору мого загального досвіду програмування, сплайсинг - це звичайно повільна робота, що включає переміщення всіх існуючих елементів на один. Я думаю, що найкращим рішенням тут буде встановити його на нуль ; мертвий прапор спрацює, але вам потрібно подбати про те, щоб не зробити ваш код безладним.

Ми насправді говорили про об'єднання ресурсів у чаті, хоча насправді. Це дуже хороша практика, і добре чути, що ви це робите. :)


1
Якщо порядок оновлення не є важливим, сплайсинг повинен бути таким же простим, як переміщення останнього об'єкта до поточного індексу та зменшення кількості пулу та інтератора.
Кай

Нічого собі, дуже хороший пункт Кай! :)
Ricket

2

Особисто я би використовував пов'язаний список. Ітерація над списком сподобався швидко, а також додавання та видалення елементів. Використання масиву чи вектора буде хорошим вибором, якщо вам потрібен прямий доступ до елементів структури (наприклад, доступ до індексу), але це не здається, що вам це потрібно.

Кожного разу, коли ви вилучаєте елемент із пов’язаного списку, ви можете додавати його до пулу об'єктів, які потім можна повторно циклізувати, щоб зберегти на розподілі пам'яті.

Я використовував полігональні структури даних у кількох проектах і був дуже задоволений ними.

Редагувати: Вибачте, я вважаю, що відповідь не була дуже чіткою з точки зору стратегії видалення: я б запропонував вилучити елемент зі списку, як тільки він мертвий, і додати його безпосередньо до структури об’єднання (переробляти). Оскільки видалення елемента із пов’язаного списку є дуже ефективним, я не бачу проблем у цьому.


1
Я припускаю, що ви пропонуєте тут подвійний зв'язаний список? (вперед / назад)? Також: Ви пропонуєте якийсь пул над елементами посилань чи динамічно розміщуєте кожного власника вказівника у зв'язаному списку?
Саймон

Так, це повинен був бути подвійний список, який найкраще підходить для цього завдання. Дякуємо, що вказали на це! Щодо повторного використання елементів: я думав про спеціалізований клас / структуру даних об’єднання, який створює нові об’єкти на вимогу або використовує існуючі екземпляри, якщо в пулі їх є. Тому було б добре видалити "мертві" елементи зі списку та додати їх до пулу для подальшого використання.
bummzack

Окремо пов’язаний список буде добре. Подвійно пов’язані списки дають лише перевагу ітерації в обох напрямках. Щоб повторити список, пов'язаний окремо, з можливістю видалення поточного елемента, вам потрібно буде відслідковувати попередній запис.
deft_code

@caspin так точно. Якщо ви використовуєте односкладений список, вам потрібно слідкувати за попередніми вузлами та зв’язувати їх nextвказівник на вузол після видаленого. Якщо ви не хочете, щоб зробити це самостійно, подвійний список буде вибором DataStructure.
bummzack

1

"просто встановіть на ній прапор, щоб сказати:" пропустіть мене, я мертвий. Я об'єдную свої об'єкти, тому Суть, яка померла, цілком ймовірно, що в якийсь момент знову оживе ".

Я думаю, ви відповіли на власне запитання щодо цієї конкретної програми. Я б ухилився від масивів, якщо ви коли-небудь плануєте робити над ними роботу, крім "push and pop". Пов'язані списки були б розумнішим шляхом, якщо ви плануєте робити важкі операції. З урахуванням сказаного, якщо ви плануєте знову інтегрувати ту саму сутність у гру, тоді є сенс встановити лише булеву змінну і перевірити її під час циклів роботи гри.


0

Одне чисте і загальне рішення, яке я знайшов на lib, який я використав, було за допомогою карти, що блокується.

у вас є 2 операції, lock()і unlock(), якщо ви повторюєте карту, ви lock()з цього моменту кожна операція, що змінила карту, не набуває чинності, вона просто підштовхується до того, CommandQueueщо буде запущено, коли ви зателефонуєте unlock().

Таким чином, для видалення об'єкта буде встановлений наступний псевдо-код:

void lockableMap::remove(std::string id) {
   if(isLocked) {
       commandQueue.add(new RemoveCommand(id));
   } else {
       //remove element from map
   }

і коли ти unlock()

isLocked = false
commandQueue.execute(this);

Єдине, що вам потрібно врахувати, це те, що ви видалите сутність лише після циклу.

EDIT: це рішення, запропоноване Саймоном.



0

У мене є два методи.

Коли ви викликаєте об'єкт, який потрібно видалити, він дійсно встановлює два прапори:

1. Один, щоб повідомити контейнеру, що об’єкт видалено

2. Один, щоб повідомити контейнеру, які об'єкти потрібно було видалити

void object::deleteObject()
{
    container->objectHasBeenDeleted = true;
    isToDelete = true;
}

Один з використанням вектора предметів

std::vector<object*> objects;

Потім у функції оновлення перевірте, чи об’єкт видалено, і якщо так, повторіть усі об’єкти та видаліть ті, у яких є прапор видалення

void container::update()
{
    if (objectHasBeenDeleted)
    {
        std::vector<object*>::iterator ListIterator;
        for(ListIterator=objects.begin(); ListIterator!=objects.end();)
        {
            if( (*ListIterator)->isToDelete )
            {
                ListIterator = objects.erase(ListIterator);
                delete *ListIterator;
            }
            else {
                ++ListIterator;
            }
        }
    objectHasBeenDeleted = false;
    }
}

Два, використовуючи (вказівник на a) вектор об’єктів.

std::vector<object*> *objects;

У функції оновлення, якщо об’єкт потрібно видалити, перейдіть через об'єкти та додайте ті, які не слід видаляти, до нового вектора. видаліть вектор об'єктів і встановіть вказівник на новий вектор

void container::update()
{
    if (objectHasBeenDeleted)
    {
        std::vector<object*> *newVector;
        unsigned long i;
        for (i = 0; i < objects->size(); i++)
        {
            if (!objects->at(i)->isToDelete)
            {
                newVector->push_back(objects->at(i));
            }
        }
        delete objects;
        objects = newVector;
        objectHasBeenDeleted = false;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.