2014년 3월 4일 화요일

[일기] Box2D, 안전하게 강체 제거하기.

강체를 제거해야 할 경우가 있다. 가령, 탄환에 맞은 유닛이라거나 폭발에 의한 주변 조형물의 경우 등이 그에 속한다.

강체를 제거하는 것은 매우 간단하다.

m_world->DestroyBody( pBody );

그러나 시점이 중요하다.

 전에 언급하였던 Box2D의 처리 과정 때문에 Step 메서드가 진행중일 때, 강체가 제거되면 계가 아직 처리해야 할 강체가 중간에 사라지는 경우가 생기기 때문에 문제가 될 수 있다.

 타이머를 통해 제거하는 방식 또한 그 시점에 아직 Step 메서드가 진행중일 수도 있으므로 안전하지 못하다. 중간에 프로그램이 종료될 수 있다.

 콜백 메서드를 통해 제거하는 것 또한 같은 이유로(Step 메서드가 진행중일 때 호출되는 메서드이므로) 예상치 못한 결과가 생길 수 있다.

 해결 방법은 매우 간단하다. 콜백 메서드나 여러 방법을 통해 특정 시점에 제거되야 할 강체가 생겼다는 것이 확정되었을 때, 이를 바로 제거하는 것이 아니라 배열이나 리스트와 같은 자료형에 모아 두고서, Step 메서드가 수행되기 직전이나(메서드 극초반부), 수행되고 난 직후(메서드 극후반부)에 이 저장된 자료형을 순회하면서 하나하나 제거하는 것이다.

#include <set>
std::set<Ball6*> ballsScheduledForRemoval;

와 같이 집합을 선언하고,

void BeginContact(b2Contact* contact){
void* bodyAUserData = contact->GetFixtureA()->GetBody()->GetUserData();
void* bodyBUserData = contact->GetFixtureB()->GetBody()->GetUserData();
if ( bodyAUserData && bodyBUserData )
handleContact2( static_cast<Ball6*>( bodyAUserData ),
static_cast<Ball6*>( bodyBUserData ) );
}
}

와 같은 충돌 콜백이 있을 때,

void handleContact2(Ball6* b1, Ball6* b2){
if ( ! outbreak )
      return;
    if ( b1->m_imIt ) {
        ballsScheduledForRemoval.insert(b1);
        b2->m_imIt = true;
    }
    else if ( b2->m_imIt ) {
        ballsScheduledForRemoval.insert(b2);
        b1->m_imIt = true;
    }
}

와 같이 집합에 집어 넣는다.

void Step(Settings* settings){


Test::Step(settings);
for( int i = 0; i < balls6.size(); i++)
balls6[i]->renderAtBodyPosition();

//process list for deletion
std::set<Ball6*>::iterator it = ballsScheduledForRemoval.begin();
std::set<Ball6*>::iterator end = ballsScheduledForRemoval.end();
for (; it!=end; ++it) {
Ball6* dyingBall = *it;

//delete ball... physics body is destroyed here
delete dyingBall;

//... and remove it from main list of balls
std::vector<Ball6*>::iterator it = std::find(balls6.begin(), balls6.end(), dyingBall);
if ( it != balls6.end() )
balls6.erase( it );
}

//clear this list for next time
ballsScheduledForRemoval.clear();
}

순회하며 Step 메서드 마지막 부분에(첫 부분도 관계없다) 모두 제거한다.

여기서 강체는 Ball6 클래스의 멤버로 묶었었다. 따라서 Ball6 클래스의 소멸자에 다음을 추가했다.

~Ball6(){
m_body->GetWorld()->DestroyBody( m_body );
}

이 결과 안전하게 강체를 제거하는 것이 가능해진다.

이런 것도 신경써야하는구나.

댓글 없음:

댓글 쓰기