tags:

views:

163

answers:

3

[SOLVED]: Applying proper list iteration procedure fixed problem. (Shown below)

I currently have a program in which elements of a list are iterated through and erased if they meet certain conditions. Due to the nature of the program, this can be visually seen.

Objects on screen that are being iterated through sometimes flicker on and off. This usually happens when objects around them are destroyed (i.e. erased in the code). At first I thought it was screen flickering, but now I realize that I think my iteration functions' logic may be causing the problem.

Here are the two functions. The first detects bullet collisions with blocks. If a bullet hits a block, the block is destroyed.

// Edit: WRONG WAY TO ITERATE THROUGH LIST
void DetectBulletCollisions()
{
    std::list<Bullet>::iterator bullet = game::player_bullets.begin();
    for ( ; bullet != game::player_bullets.end(); ++bullet)
    {
        if (bullet->IsOnScreen())
        {
            bullet->DetectBlockCollision(game::blocks);
        }
        else // Remove bullet from list
        {
            bullet = --game::player_bullets.erase(bullet);
        }
    }
}

This function moves the blocks that are flickering.

// Edit: RIGHT WAY TO ITERATE THROUGH LIST
void MoveBlocks(const int delta_ticks)
{
    // Blocks on screen
    std::list<Block>::iterator block = game::blocks.begin();

    while (block != game::blocks.end()) // Loop through blocks
    {
        block->Show(); // Show block
        if (!block->IsDestroyed()) // If block hasn't been destroyed
        {
            block->Move(delta_ticks); // Move block
            ++block; // Increment iterator
        }
        else // Block has been destroyed, remove it from list.
        {
            block = game::blocks.erase(block);
        }
    }
}

Is there something wrong with the logic of these loops? Notably the second one? It seems that when a block is destroyed, others around it flicker on and off (it isn't consistent, but that may just be frame rate). I'm not sure if the list rearranging the elements after each erasure would be a problem or not. The blocks each have coordinates, so it doesn't matter where they are in the list.

If any more information is needed I'd be happy to give it. I'm just wondering if my logic is wrong in the writing of these loops, or if I should take a different approach. I chose lists because they are the most efficient STL container for removing elements.

+1  A: 

In both loops, when you erase an element, you assign the return value of erase to the loop iterator. According to cplusplus.com, list::erase returns the element after the erased element. So that code will always skip a bullet or a block when an erase happens if I'm not mistaken. Could that have anything to do with it?

henle
I will look into this now.
trikker
Yes, this was the exact problem. I erased the element, and assigned it decremented to the iterator. Thanks very much!
trikker
great! :) you could also move the increment into the loop body so that you're not forced to do the slightly hairy decrement.
henle
A: 

Couldn't you use the double-buffering technique where you work on a background buffer for all these operations and once it is done you swap it out with the current front one so all the changes are done at once which would remove these flickers while you go through the list.

David
That is what I always do. I apply everything to the buffer, then once the loop is finished I update the screen with that buffer, if that is what you meant. Then obviously I clear the buffer and redo the process.
trikker
A: 

Your solution is still wrong. What if you erase the first item? You are not allowed to decrement the iterator to the beginning of a container.

A typical "erase while iterating" loop looks like this:

void DetectBulletCollisions()
{
    std::list<Bullet>::iterator bullet = game::player_bullets.begin();
    while (bullet != game::player_bullets.end()) //NB! No incrementing here
    {
        if (bullet->IsOnScreen())
        {
            bullet->DetectBlockCollision(game::blocks);
            ++bullet; //Only increment if not erased
        }
        else // Remove bullet from list
        {
            bullet = game::player_bullets.erase(bullet); //iterator "incremented" by erase
        }
    }
}
UncleBens
Very helpful, thanks. For some reason I was stuck in a for loop mindset.
trikker