코드 생성을 사용해 개발 속도 높이기 김이선 (veblush at neople | gmail) 네오플 던파개발실
코드 생성을 사용해
개발 속도 높이기
김이선 (veblush at neople | gmail)
네오플 던파개발실
BNB 프로그래머
카트라이더 리드 프로그래머
버블파이터 프로토타입
리드 프로그래머
에버플래닛 리드 프로그래머
던전엔파이터 테크니컬 디렉터
게임 프로그래밍 10년차
생산성!
생산성은 왜?
1. enum 으로 보는 코드 생성
2. CodeGen 3. 에버플래닛의 사용 예
enum Fruit { apple = 1, banana, grape };
const WChar* Fruit_strs[] = { L"apple", L"banana", L"grape" }; void fillValue(CComboBox* cb) { cb->Clear(); for (int i=0; i<_countof(Fruit_str); i++) cb->AddString(Fruit_strs[i]); }
enum 에게 필요한 것은?
enum ↔ 문자열 String toString(Fruit f)
banana(2) <-> L"banana"
멤버 iteration for (f in Fruit) { print f } apple, banana, grape
Macro | Template 이라면?
ENUM_BEGIN(Fruit) ENUM_V(apple, 1) ENUM(banana) ENUM(grape) ENUM_END(Fruit)
#define ENUM_BEGIN(t) \ char* t ## _table [] = { #define ENUM(n) #n, #define ENUM_END(t) };
…
적은 비용. 외부 의존성 낮음. 때문에 좋은 해결책이기도 하다
기능 추가에 유연하지 않음. 코드 마술이 늘 쉬운 것만은 아님
기술 코드
C++ 헤더 코드
C++ 소스 코드
코드 생성
<Enum name="Fruit"> <C n="apple"/> <C n="banana"/> <C n="grape"/> </Enum>
struct Fruit { enum T { apple, banana, grape }; }; String toString(Fruit::T v) { … } void fromString(WChar* str, Fruit::T* v) { … }
구현 기능에 제한이 없음 코드로 할 수 있다면 뭐든지!
코드 생성 툴에 종속성이 생김 개발 과정에 꼭 필요한 툴 추가
구현 가능성 > 툴 종속성
1. enum 으로 보는 코드 생성
2. CodeGen 3. 에버플래닛의 사용 예
CodeGen ?
코드 생성을 수행하는 Visual Studio 플러그인
(C# LOC:3K)
소스 작성 중
CodeGen 코드 작성
CodeGen 플러그인 실행
소스 작업 계속
C++ 코드 생성
CodeGen 데모
http://www.youtube.com/watch?v=6wfNfeK_t0w
소스 편집 중에 바로 코드 생성
생성된 코드를 바로 확인! Intellisense!
빌드 자체에는 툴 종속성이 없음
작업 코드 중간에 들어갈 수 있음
코드 생성을 위해 별도 파일 불필요
유저 코드 삽입이 자연스러움
코드 생성으로 얻는 것?
빠르게 원하는 기능 구현 구현 코드는 CodeGen 이 생성! 프로그래머는 내용에 집중!
신뢰성/일관성 있는 코드 기계적으로 생성하는 코드!
사소하지만 필요한 기능
<Enum name="Fruit" dllDecl="LibDecl"> <Const name="apple" value="1"/> <Const name="banana"/> <Const name="grape"/>
struct LibDecl Fruit // LibDecl=__declspec(dllexport) … { enum T { apple = 1, banana, grape,
라이브러리 형태 지원
<!--! 과일을 나타냅니다 --> <Enum name="Fruit"> <Const name="apple" value="1" comment="사과"/> <Const name="banana" comment="바나나는 맛있어"/>
//! 과일을 나타냅니다 struct Fruit { enum T { apple = 1, //!< 사과 banana, //!< 바나나는 맛있어
doxygen 주석 붙이기
<Enum name="Fruit"> <Const name="apple" value="1"/> … <HText><![CDATA[ Bool isMyFavorate(Fruit::T v); ]]></HText>
struct Fruit { enum T … Bool isMyFavorate(Fruit::T v); };
생성된 코드에 유저 코드 삽입
주의사항?
문법은 확장 가능하도록! 유연하게! 일반적인 문법! XML?
생성된 코드가 수정되지 않게! 기계적인 느낌이 나도록 생성? #pragma region?
모두가 같은 버전의 툴! 예전버전을 사용하면 곤란. 자동 업데이트?
1. enum 으로 보는 코드 생성
2. CodeGen 3. 에버플래닛의 사용 예
기계적이고 반복적인 부분에 적용. 적용 영역이 지속적으로 확대.
적용한 부분은?
사용한 CodeGen 영역
Enum
Packet Data
DbCall DbRowset
Enum : 열거형 확장
문자열 변환 기능 추가 멤버 순회 기능 추가
Enum : CodeGen
<Enum name="Fruit"> <Const name="apple" value="1"/> <Const name="banana"/> <Const name="grape"/> </Enum>
Enum : Generated
struct Fruit { enum T { apple=1, banana, grape }; … }; String toString(…); fromString(…);
Enum : Usage
toString(Fruit::banana); // "banana" fromString<Fruit::T>(L"grape"); // 3 Fruit::enumCount; // 3 Fruit::enumValue[1]; // Fruit::banana
template <class EnumType> void fillValue(CComboBox* cb) { cb->Clear(); for (size_t i=0; i<EnumType::enumCount; i++) cb->AddString(EnumType::enumStr[i]); };
Packet : 패킷 Serialization
패킷 내용을 구조체로 구성 Byte Serialization 코드 작성
패킷은 지속적인 작업 귀찮은 Serialization 코드 작업
Packet : CodeGen
<Packet name="TestPacket"> <Field name="code" type="Int4"/> <Field name="name" type="String"/> </Packet>
Packet : Generated
struct TestPacket : public Packet { TestPacket(Int4 code, String name); virtual Int4 getStreamSize() const; virtual void readFrom(Stream& is); virtual void writeTo(Stream& os); public: Int4 code; String name; };
Packet : Usage
send() { TestPacketPtr p = new TestPacket; p->code = 1; p->name = L"Test"; sendPacket(p); // or sendPacket(new TestPacket(1, L"Test")); } recv(TestPacket* p) { p->code; p->name; }
패킷 멤버 모두가 단순 타입이면 read/write 를 단순 memcpy로
코드 생성이라 최적화 전략 가능
Packet : Optimized
// Member Int4 userId; Int4 itemId; Int4 presetId; Vector2 pos;
// Read is >> userId; is >> itemId; is >> presetId; is >> pos;
// Read (Optimized) is.read(this, sizeof(TestPacket));
Data : POD 확장
멤버 변수를 XML | 바이트 스트림으로
Serialization
아이템, 퀘스트 등을 기술하는데 이러한 구조체가 필요
이러한 데이터는 파일에 있기 때문에 I/O 코드 필요
Data: CodeGen
<Data name="Item"> <Field name="id" type="Int4"/> <Field name="name" type="String"/> <Field name="desc" type="String"/> <Field name="level" type="Int4" init="0"/> <Field name="itemClass" type="ItemClass::T" init="ItemClass::kMisc"/> </Data>
Data : Generated
struct Item { Item(); void readFrom(Stream& is); void writeTo(Stream& os) const; void readFrom(XmlElement* xel); void writeTo(XmlElement* xel) const; Int4 id; String name; … };
Data : Usage
Item item; item.readFrom(xml); item.id; item.name;
<Item id="100" name="무쇠 갑옷" desc="무척강하다" itemClass="armor" />
Data CodeGen 데모
Pet 구조체 만들기 ItemSpawn 은 이미 있음
http://www.youtube.com/watch?v=kRrYj3Lx5Pw
용량 16% 속도 x3
<Pet id="10" name="monkey" food="banana" > <Items> <ItemSpawn itemId="100" /> <ItemSpawn itemId="200" count="5" /> </Items> </Pet>
07 0a 00 00 00 06 00 00 00 6d 00 6f 00 6e 00 6b 00 65 00 79 00 02 00 00 00 02 00 00 00 00 64 00 00 00 01 c8 00 00 00 05 00 00 00
개발 때는 XML 사용 포맷 개방, 공동 작업!
배포 때는 Binary 사용 포맷 고정, 빠른 속도, 보안!
DbCall : SQL/SP 호출
호출 전후에 파라미터 변수 바인딩
DbCall : CodeGen
<DbCall name="DcLogin" spName="spLogin"> <Param name="id" type="String" maxLen="24"/> <Param name="ret" type="Int4" io="out"/> </DbCall>
CREATE PROCEDURE spLogin @id as nvarchar(24), @ret as int OUTPUT
DbCall : Generated
struct DcLogin { static HRESULT exec(DbSession* s, const String& id, Int4* ret); struct Param : public DbCallParam { String id; Int4 ret; virtual HRESULT exec(DbSession* s); }; };
DbCall : Usage
hr = DcLogin::exec(s, "userid", &ret); // or p = new DcLogin::Param; p->id = "userid"; p->exec(s); p->ret;
CREATE PROCEDURE spLogin @id as nvarchar(24), @ret as int OUTPUT
DbRowset : Rowset 읽기
Rowset 을 읽어 컬럼 변수에 바인딩
DbRowset : CodeGen
<DbRowset name="DrInven"> <Field name="id" type="Int4"/> <Field name="count" type="Int4"/> <Field name="expireTime" type="DateTime4"/> </DbRowset>
SELECT id, count, expireTime FROM tblInven WHERE charId=1
DbRowset : Generated
struct DrInven : public DbRowset { Int4 id; Int4 count; DateTime4 expireTime; protected: virtual HRESULT onInit(...); virtual void onNext(); };
DbRowset : Usage
DrInvenPtr rs; hr = DcInvenGet::exec(dbSession, &rs); while (rs->next()) { rs->id; rs->count; rs->expireTime; }
SELECT id, count, expireTime FROM tblInven WHERE charId=1
적용 비중?
사용한 CodeGen 개수
0 200 400 600 800 1000
Enum
Packet
Data
DbCall
DbRowset
CodeGen 코드 라인 수
원본코드
10,946
생성코드
69,391
x7
생성코드의 비중
생성코드
69,391
그외코드
559,517
11%
Total: 11261 revs
Packet:
1646 revs (14%) x2
Data/Item:
215 revs (2%) x215
결론
코드 생성은 생각보다 많은 영역에 사용 가능 기대보다 많은 코드 작성을 대신
시도해보자!
감사합니다!