프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.
Posted by 끼로

보통 통신을 할때에 메시지ID를 사용합니다.

보내는쪽과 받는쪽이 이 메시지ID로 구분하여 어떤것때문에 보냈는지를 알 수 있습니다.


namespace eMessage
{
enum Type
{
Test1,
Test2,
Test3,
...
};
}


그런데 문득 이런생각이 들었습니다. 

'메시지ID가 보내고 받는 데이터의 타입도 같이 가지고 있으면 좋겠다..'

그래서 만들어보았습니다.


struct MessageTable
{
Message<unsigned int> Test1;
Message<unsigned int, const Vector3&> Test2;
Message<int, const std::vector<int>&> Test3;
...
};

extern const MessageTable MessageType;


(네.. 저는 템플릿매니아..)

대충 이런식으로 선언을 하고 MessageType.Test1 이 메시지의 객체가 됩니다. 

'멤버변수의 초기화는 선언한 순서대로 초기화가된다' 라는 특성을 이용하여

MessageType.Test1.GetMessageID() 는 0

MessageType.Test2.GetMessageID() 는 1

MessageType.Test3.GetMessageID() 는 2

가 되도록 Message들의 가상 상위 클래스인 MessageBase에 static 변수를 하나 만들고 카운팅을 해주었습니다.


// MessageBase.h
class MessageBase
{
static unsigned short s_count;
...
};


// MessageBase.cpp
unsigned short MessageBase::s_count = 0;

MessageBase::MessageBase()
: m_messageID( s_count++ )
{
}


그러면 타입은 어떻게 가지고 있는걸까요?


template<typename D1=void, typename D2=void>
class Message : public MessageBase
{
public:
typedef D1 DataType1;
typedef D2 DataType2;

typedef typename type_traits::remove_cv<typename type_traits::remove_reference<D1>::type>::type DataBaseType1;
typedef typename type_traits::remove_cv<typename type_traits::remove_reference<D2>::type>::type DataBaseType2;
...
};


이쯤 보셨으면 도대체 이걸 어따가 쓰지? 하는 생각이 드실지도 모르겠는데요.. 저는 이런식으로 사용합니다.


template<typename T>
void SendMessage( const T& messageType, typename T::DataType1 d1 )
{
...
}


SendMessage( MessageType.Test1, 100 );

이렇게 호출하면 SendMessage 함수 안에서 컴파일타임에 메시지에서 사용하는 타입인지 체크가 가능합니다!


그런데 이런걸 만들면 도대체 뭐가 좋은거지?! enum은 메모리도 안먹는데!!

이건 메시지타입 하나당 메모리도 메시지ID의 타입만큼(예제에선 unsigned short)씩 먹자나!!

라고 하실 분들이 많으실텐데.. 전 편하게 쓰고 있습니다.....


편한것들 중 한가지를 말해보자면..

메시지ID가 데이터타입을 알고 있기 때문에 버퍼에 데이터를 넣거나 빼는것도 제공할 수 있습니다.


// MessageBase.cpp
void MessageBase::PushMessageID( std::streambuf& buffer, const MessageBase& messageType )
{
boost::archive::binary_oarchive oa( buffer, boost::archive::no_header );
oa <<  messageType.m_messageID;
}

void MessageBase::PopMessageID( std::streambuf& buffer, unsigned short& messageID )
{
boost::archive::binary_iarchive ia( buffer, boost::archive::no_header );
ia >> messageID;
}


// Message<D1, D2>
template<typename D1=void, typename D2=void>
class Message : public MessageBase
{
...
void Push( std::streambuf& buffer, DataType1 d1, DataType2 d2 ) const
{
boost::archive::binary_oarchive oa( buffer, boost::archive::no_header );
oa << d1;
oa << d2;
}

void Pop( std::streambuf& buffer, DataBaseType1& d1, DataBaseType2& d2 ) const
{
boost::archive::binary_iarchive ia( buffer, boost::archive::no_header );
ia >> d1;
ia >> d2;
}
};


SendMessage에서는 이렇게..


template<typename T>
void SendMessage( const T& messageType, typename T::DataType1 d1, typename T::DataType2 d2 )
{
if( m_pServerSession != NULL )
{
MessageBuffer buffer( 512 );
MessageBase::PushMessageID( buffer, messageType );
messageType.Push( buffer, d1, d2 );
m_pServerSession->Send( buffer );
}
}


받는 부분에선..


unsigned short messageID;
MessageBase::PopMessageID( messageBuffer, messageID );

...

int data1;
std::vector<int> data2;
MessageType.Test3.Pop( messageBuffer, data1, data2 );


이런식으로 사용하고 있습니다..

템플릿에 익숙하면 여기저기에서  유용하게 쓰일 수 있지만

템플릿을 안쓰는 분들이면 쓸곳이 없긴 하겠네요..

저는 템플릿 매니아..



댓글을 달아 주세요

  1. Favicon of http://bluekms21.blog.me 크로스 2012.06.19 11:35  댓글주소  수정/삭제  댓글쓰기

    이렇게 호출하면 SendMessage 함수 안에서 컴파일타임에 메시지에서 사용하는 타입인지 체크가 가능합니다!
    이 부분의 문장은

    이렇게 호출하면 컴파일타임에 sendMessage() 안에서 메시지의 타입체크가 가능합니다!
    라고 이해하면 맞는지요..?

    그리고 상당히 괜찮아보이네요... 역시 템플릿은 좋은겁니다..ㅎ_ㅎ

    아, 또하나 질문은 static변수에 관한건데요...
    제가 예전에 레퍼런스카운팅에 대해 처음 배울때 static으로 만들겠다는 야심찬 꿈을 가지고 있던 터라...
    이렇게 만들면 프로그램 시작부터 끝까지 저 메시지 시스템을 사용하겠다는거죠?

    • Favicon of https://gamedevforever.com 끼로 2012.06.19 11:55 신고  댓글주소  수정/삭제

      아 SendMessage() 안에서 메시지를 보낼때 같이 보내야 하는 데이터가 맞는지와 데이터가 덜 입력됐는지 등등이 모두 체크가 가능하다는 말이었습니다. 그리고 저는 이 메시지를 네트워크 통신에 사용을 하는데.. 보통 enum으로 만드니까 그거랑 비슷한 생성주기를 가져야 한다고 생각했습니다. 물론 enum은 실제로 생성이 되는 변수는 아니지만요 사용하는 측면에서말이지요.. 확실히 메모리를 차지하고 실제로 생성되는 '변수'이다보니 무조건적으로 좋다고 볼수는 없겠지만.. PC 온라인게임을 만드는 입장에서는 꽤 편하게 쓰고 있습니다..

  2. Favicon of http://bluekms21.blog.me 크로스 2012.06.19 20:57  댓글주소  수정/삭제  댓글쓰기

    @끼로
    좋은 답변 감사드립니다. 저는 늘 static 변수를 쓸때 애매하더라고요..;; 그래서 여쭤본 겁니다.
    좋은 노하우 전수해주셔서 감사합니다~

  3. Favicon of http://dalinaum-kr.tumblr.com 달리나음 2012.06.21 08:00  댓글주소  수정/삭제  댓글쓰기

    데이크스트라는 그의 레터에 `Goto statement considered harmful.'이라는 이름이 붙은 것에 대해 데이크스트라는 오랜 시간동안 짜증냈습니다. 데이크스트라는 프로그램의 주 흐름에서, 널 뛰는 문맥 포인터를 따라가는 것은 인간에게 적합한 방식이 아니라고 보고 이 널 뛰는 문맥 포인터를 구조적 프로그래밍으로 해결할 수 있다고 본 사람이었습니다. 그리고 주 흐름이 아니라면 널 뛰는 포인터가 구조적 프로그래밍으로 완전히 대체될 수 없다고 생각했습니다. 주 흐름 밖에서 goto 문을 쓰는 것에 대해서는 인정을 했다고 보는게 맞다고 생각됩니다.

    • Favicon of https://gamedevforever.com 끼로 2012.06.21 09:53 신고  댓글주소  수정/삭제

      댓글을 잘못 다신것 같네요.. 아무래도 도플광어님의 코루틴 이야기에 대한 댓글 같은데..

    • Favicon of http://dalinaum-kr.tumblr.com 달리나음 2012.06.21 23:45  댓글주소  수정/삭제

      그러게요... 이 글을 읽지도 않았는데 어떻게 여기에 댓글이 달리게 되었는지 신기합니다. 끼로님께 죄송하고요. 글을 읽어보겠습니다 (_._)