Nebula Mangalore

엔터티 시스템의 메시지 구조를 이해하기 위해 Nebula 엔진의 망갈로를 분석해 보았다.
디버깅 걸고 소스  분석을 했기 때문에 정확한 분석은 아니다. 주로 엔터티, 컴포넌트간의 메시지 흐름에 대해서 분석 하였다. 망갈로에서는 컴포넌트를 Propery라 부르니 이장에서는 앞으로 프로퍼티를 컴포넌트로 생각한다.

용어부터 정리한다.

Entity : 언터티 클래스
Property : 컴포넌트
Port : 메시지 포트 베이스 클래스, 메시지를 처리 할려면 Port나 Dispatcher를 상속한다.
Dispatcher : Port를 상속한 클래스로 Port 배열을 추가적으로 가진다.
Server : Port를 전체적으로 관리하는 클래스

< 엔터티에서 dispatcher 관리 >

엔터티는 dispatcher 멤버 변수를 가진다.
Ptr<Message::Dispatcher> dispatcher;

dispatcher를 메시지 서버에 등록한다. 프로퍼티의 메시지를 처리하기 위해 ActivateProperties() 함수를 부른다.
메시지 서버로 Port 등록과 프로퍼티 Port 등록이 끝나면 activated와 isInOnActivate 플래그를 셋팅한다.

void    Entity::OnActivate()

{

    ..........

    // attach dispatcher to message server

    Message::Server::Instance()->RegisterPort( this->dispatcher );

    ..........
    // activate all properties
    this->ActivateProperties();

    ..........
    // set activated flag
    this->activated = true;
    this->isInOnActivate = false;
    ...........

}

 

void    Entity::ActivateProperties()

{

    int num = this->properties.Size();

    for ( int i = 0; i < num; i++ )

    {

        this->dispatcher->AttachPort( this->properties[i] );

        this->properties[i]->OnActivate();

    }

}

 

< Server에서 dispatcher 관리 >

Server는 Port를 관리하는 클래스로 Port를 관리하고 Msg 흐름을 제어한다.
Port  배열을 가진다.

nArray<Ptr<Port> > portArray;

주요 멤소드는 다음과 같다.

//특정한 포트로 동기화 메세지를 보낸다. 

void    SendSync(Port* port, Msg* msg);

//등록된 모든 포트로 동기화 메세지를 보낸다.

void    BroadcastSync(Msg* msg);

//특정한 포트로 비동기화 메세지를 보낸다. 

void    SendAsync(Port* port, Msg* msg);

//등록된 모든 포트로 비동기화 메세지를 보낸다.

void    BroadcastAsync(Msg* msg);

Msg 클래스의 메소드는 다음과 같이 들어가 있다.

class    Msg : public    Foundation::RefCounted

{

    ........

    virtual    void    SendSync(Port* port);

    virtual    void    BroadcastSync();

    virtual    void    SendAsync(Port* port);

    virtual    void    BroadcastAsync();

    ........

};

Entity 클래스의 메소드는 다음과 같이 들어가 있다.

class    Entity : public    Foundation::RefCounted

{

    .......

    void    SendSync(Message::Msg* msg);

    void    SendAsync(Message::Msg* msg);

    .......

};

< Msg와 Port >

Msg 클래스는 돌아 다니는 메시지, 정보의 흐름이다. 탁구로 치면 탁구공이고 Port는 탁구공을 처리하는 탁구채, 즉 정보를 처리하는 곳이다.

지금 예제에세는 Msg 클래스를 상속한 MoveGoto, SetTransform, UpdateTransform 클래스가 있다.
메시지를 상속한 클래스에 필요한 정보를 넣어서 Port를 상속한 클래스에서 HandleMessage 가상함수를 구현하면 된다.

여기서는 TransformableProperty 클래스에서 Message::UpdateTransform와 Message::SetTransform 두 메세지를 처리하고 있다.

void      TransformableProperty::HandleMessage(Message::Msg* msg)

{

    .............

    if (msg->CheckId(Message::UpdateTransform::Id))

    {

        // update the transformation of the game entity

        Message::UpdateTransform* updateTransform = (Message::UpdateTransform*) msg;

        this->GetEntity()->SetMatrix44( Attr::Transform, updateTransform->GetMatrix() );

    }

    else  if (msg->CheckId(Message::SetTransform::Id))

    {

        .............

    }

    else

    {

        Game::Property::HandleMessage(msg);

    }

}

위에서 id 타입은 클래스로 별도의 형이 있는 것이 아니라 포인터로 검사한다.
id::operator==() 보면 알수 있다.

class Id

{

public:

    Id();

    bool operator==(const Id& rhs) const;

};

 

inline bool Id::operator==(const Id& rhs) const

{

    return (this == &rhs);

}

< 메세지 호출에 따른 호출 >

호출 스택: message.txt

메시지는 적용범위와 실행 시간에 따라 부르는 방법이 6가지이다. 방송은 모든 Port에 메시지를 보내는 구조일 것이다( 아무도..맞을 것이다. ).

적용 범위

동기화

비동기화

모든
컴포넌트

gameEntity->SendSync( msg )

gameEntity->SendAsync( msg ) Managers::EntityManager::Instance()->OnFrame()

특정
컴포넌트

msg->SendSync( component )

msg->SendAsync( component ) Managers::EntityManager::Instance()->OnFrame()

방송

msg->BroadcastSync()

msg->BroadcastAsync()
Managers::EntityManager::Instance()->OnFrame()

엔터티 구조의 클래스를 만드는데 제일 힘든 것은 엔터티, 컴포넌트간의 메세지 관련 부분이다.
이글을 참고삼아 깔끔한 언테티 구조를 만들기 바란다.

nebula_mangalore 프로젝트는 뉴볼라 엔진이 컴파일이 쉽지가 않아서 망갈로 부분만 별도로 발췌하여 만든 프로젝트이다.

프로젝트 : nebula_mangalore.zip