Successfully reported this slideshow.

이승재, M2 AI코드 개발 생산성 향상 사례, NDC2013

27

Share

Loading in …3
×
1 of 62
1 of 62

이승재, M2 AI코드 개발 생산성 향상 사례, NDC2013

27

Share

Transcript

  1. 1. C++에서 극한의 생산성 뽑아내기 M2 AI코드 개발 생산성 향상 사례 코드 핫로딩과 자체 제작 스크립트 언어 넥슨코리아 W엔지니어링팀 이승재
  2. 2. 카바티나 스토리 2007~2009 데스크탑 히어로즈 2010~2011 마비노기2 2011~ 이승재 프로그래머
  3. 3. 어젠다 1. 런타임에 코드를 교체하는 기법을 소개하고, 2. C++에 찰싹 달라붙는 스크립트 언어를 만들어본 경험을 공유합니다.
  4. 4. 작년가을 “AI를 맡으세요” “헉” 미션: 기존 코드 튜닝, AI 패턴 추가
  5. 5. AI 작업의특징 • 외부 코드에 많이 의존한다. • 시간의 흐름에 따라 연속적으로 변화한다. • 자연스러운 행동이란 뭐지? • 군집 행동의 테스트 케이스는 어떻게 하지? 유닛 테스트는 어렵겠고… 이터레이션 속도 = 생산성!
  6. 6. 이터레이션 • 코드 수정 • 빌드 • 로딩 • 테스트 이 루프가 1분 이상;;
  7. 7. 일반적인해결책들 • 컴파일 시간: PCH, 의존성 감소 • 링크 시간: DLL 빌드 • 로딩 시간: 로딩 최적화 이미 잘 적용되어 있고 내가 손대기 어려운 것들…
  8. 8. 이래선, 안되겠어. 빨리 어떻게든 하지 않으면 ….
  9. 9. 1. 실행 중 DLL 교체 2. 스크립트 언어 자체 제작
  10. 10. 10:41:13 김주복 [EIAS] 간단히 말하면 플러그인이잖......
  11. 11. 플러그인? • Dynamic Linked Library (DLL) – 실행 시점에 코드를 메모리에 로드한다 • DLL 링크 방법 – Implicit Link (묵시적 링크) – Explicit Link (명시적 링크)
  12. 12. ImplicitLink • 보통 접하는 DLL 빌드__declspec(dllimport) • 라이브러리 프로젝트 빌드하면 .lib와 .dll 생성됨 • 사용하는 쪽에선 .lib을 스태틱 링크한다. • .lib이 초기화될 때 .dll을 찾아서 자동으로 로드한다. – DLL의 생명주기를 통제할 수 없다. – 언로드하거나 교체할 수 없다.
  13. 13. ExplicitLink • HMODULE WINAPI LoadLibrary( LPCTSTR lpFileName); // DLL을 로드한다. • typedef int (FAR WINAPI *FARPROC)(); • FARPROC WINAPI GetProcAddress( HMODULE hModule, LPCSTR lpProcName); // 함수 주소를 얻는다. 원리는 정말 간단
  14. 14. 문제와 해결책들
  15. 15. 진입점문제 extern “C” 로 묶어주지 않으면 이름이 바뀐다!;;네임 맹글링;; 노출하는 함수가 많으면 피곤하다. 일일이 GetProcAddress 해야 하므로.. 함수 시그니처 어긋남을 컴파일러가 감지하지 못한다.
  16. 16. 진입점문제:해결책 • 인터페이스 클래스를 라이브러리와 호출부가 공통 참조하고, 이것을 상속·구현한 객체를 라이브러리 함수가 리턴하게 했다. • GetProcAddress 로 단 하나의 함수만 불러오면 된다. • COM의 QueryInterface와도 비슷하다.
  17. 17. 외부심볼참조문제 • Explicit Link DLL은 EXE의 심볼을 볼 수 없다. • 프로그램의 다른 부분이 Implicit Link DLL로 되어 있다면 추가 비용 없이 그대로 사용 가능하다. • 아니라면 좀 어려운 문제.
  18. 18. DLL안전하게해제 DLL 해제하기 전에, 내부로의 연결을 모두 끊어야 한다. 빠뜨리면 크래시! → DLL을 해제하지 않게 해서 해결. 코드 변경할 때마다 계속 중복 로드하게 함
  19. 19. DLL덮어쓰기 로드된 DLL은 덮어쓰기 불가능.. 빌드가 되지 않는다! → 로드 전에 DLL 파일 이름을 랜덤하게 바꾸어서 해결 이름이 비슷한 DLL 파일이 계속 쌓인다! → 클라이언트 처음 뜰 때 검색해서 삭제
  20. 20. 빌드구성 • 릴리스 버전에는 DLL 교체가 필요 없다. • 스태틱 링크가 가능할까?
  21. 21. 빌드구성 디버그 계열 빌드 릴리스 계열 빌드 라이브러리의 출력 형태 DLL 정적 라이브러리 사용하는 프로젝트에 추가 종속성 설정 안 함 링크될 .lib 이름 EXPLICIT_LOAD_LIBRARY(“AI.dll”) LoadLibrary(“AI.dll”) 사라짐 EXPLICIT_GET_PROC_ADDRESS( h, FuncType, Func) (FuncType)GetProcAddress( h, “Func”) &Func ※ 공통: 사용하는 프로젝트에서 참조 프로젝트로 라이브러리 프로젝트를 추가하되, 라이브러리 종속성은 끈다. 빌드 순서는 유지하되 Implicit link는 막기 위함
  22. 22. 행복해졌습니다 한동안은..
  23. 23. 1. 실행 중 DLL 교체 2. 스크립트 언어 자체 제작
  24. 24. 기존AI코드 • C++, 스테이트 패턴 • AI 종류별 설정 파일이 존재 거대한 고정 파이프라인C++이 있고 이것을 제어하는 스위치들데이터이 잔뜩 붙어있는 형태
  25. 25. 기존코드의문제 • 복잡도가 아주 빠르게 증가한다. – 기존 코드를 최대한 활용하고, 필요한 곳에서 분기하도록 만들게 되는 경향이 있다. – 보스는 전용 기능 덩어리.. • 디버깅하기 어렵다. – 이 설정 항목의 의미는 뭐지? – 얘는 왜 이렇게 행동하지?
  26. 26. 대안: 스크립트로이식하자 • 거대 고정 파이프라인을 해체하자. – 예) 기존: ‘공격중’ 스테이트를 많은 AI종류가 공유 대안: 각 AI 스크립트에 ‘공격중’ 스테이트를 둔다 • 중복이 있더라도 부담 없도록, 표현이 간결해야 한다. • 비슷한 유형은 묶어서 표현할 수 있도록 하자.
  27. 27. 선택지 • Lua • 다른 게임 스크립팅 언어들 • 자체 제작
  28. 28. 선택지: Lua • 장점: – 널리 알려져 있다. – 나도 익숙하다. • 단점: – FSM을 원하는 만큼 간결하게 표현 못한다. – 단순 실수를 컴파일 타임에 잡지 못한다! NDC’11 <온라인 게임 처음부터 끝까지 동적언어로 만들기> 참고.
  29. 29. 선택지: 다른게임스크립팅언어들 • AngelScript, Squirrel, GameMonkey, … • 잘 몰라서 탈락. 어떤 특성이 있는지, 어떤 함정이 있는지…
  30. 30. 선택지: 자체제작 • 할 수 있을까? • 정말 좋을까? • 비용은 얼마나 들까?
  31. 31. 할수있을까? Lua 5.0 구현 논문과, Lua로 된 Lua 파서 코드를 봤었다. 딱히 외계인 기술이 아니다, 도전해 볼만한 일이라고 판단.
  32. 32. 정말좋을까? • 전에 해 봤다: FSM 언어 – 카바티나 스토리 – 캐릭터 조작계와 AI에 사용 – 간단한 구조 변수와 Expression 없음, 한정된 형태의 제어문 – 적은 투자로 큰 효과를 봤던 기억 [시작] 전투중이면 -> 가만서있는다=0.5, [전투중] !다돌아왔으면 -> 제자리로돌아간다, [돌아가기] 무조건 -> 배회한다=$배회주기 [전투중] !전투중이면 -> 제자리로돌아간다, [돌아가기] 스킬사용해본다( $스킬_평타 ) -> 시전성공 무조건 -> 가만서있는다=0.1 [돌아가기] 전투중이면 -> 가만서있는다=0.5, [전투중] 다돌아왔으면 -> 가만서있는다=0.5, [시작] 무조건 -> 제자리로돌아간다
  33. 33. 비용은얼마나들까? • 개인 시간에 proof-of-concept을 시도해 보았다. • if문과 변수 선언을 갖는 언어를 구현하는 데 며칠 걸림. 할 수 있겠다!
  34. 34. HFSMHierarchicalFiniteStateMachine 스테이트 머신을 위한 문법 실행 중 일시 정지 한글 식별자 멤버 변수 초기화 구문 강조
  35. 35. HFSM 언어: 잘 한 것 주로 언어의 모양과 기능
  36. 36. 스테이트머신을표현하는문법 • [State] • # Update { … } • # Enter { … }, # Leave { … } 실제로는 거의 사용하지 않았다. • if (…) { goto [State]; }
  37. 37. 실행중일시정지 • yield; • 홀드(30)~; • 공격준비동작실행(…)~; (일종의) 코루틴. 함수호출이 없으므로 중간 상태 보관 구현이 아주 간단했다. 잘 쓰면 스테이트 개수를 아주 많이 줄일 수 있다.
  38. 38. 컴파일타임타입체크 • 모든 문Statement의 유효성과 식Expression의 타입을 컴파일 타임에 체크한다. – 컴파일 타임 = 스크립트 로드 시점 – 바인딩 정보를 활용한다. 일단 로딩에 성공하면 스크립트 실행중 에러가 나지 않는다. 격렬한 리팩토링도 무섭지 않다.
  39. 39. 사용자정의타입 • 클래스의 바인딩을 C++ 코드로 작성한다. • 스크립트에서 바로 멤버 변수를 읽고 쓸 수 있다. • 스크립트에서 바로 멤버 함수를 호출할 수 있다. 스크립트에서 직접 클래스를 선언하는 문법을 만들지 않았다.
  40. 40. 멤버변수초기화 var 탈것서브어택 = AttackType() { 준비시간 = MinMax(40, 45), 공격액션 = "SubAttack" }; var 탈것서브어택 = AttackType(); 탈것서브어택.준비시간 = MinMax(40, 45); 탈것서브어택.공격액션 = "SubAttack"; • C#을 모방. • 복잡한 데이터 서술에 좋다. • 일반적인 데이터 로딩에도 활용할 수 있을 것 같다. =
  41. 41. 파생스크립트 • 비슷한 AI들이 많다. • 다 따로 만들면 관리하기 어렵다. 아직 코드가 굳어지기 전에는 더욱. • 상속 같은 걸 끼얹나?
  42. 42. 파생스크립트 • 스탠드얼론 AI 스크립트 – 모든 기능을 사용할 수 있다. – 행동의 절차를 서술한다. • 확장 스크립트 – extend XXXX; 로 시작한다. – Init 전역 함수만 만들 수 있다. – 수정이 필요한 설정이나 전역변수를 Init에서 변경한다.
  43. 43. 파생스크립트 “예전 구조랑 똑같지 않나요?” • 한 AI = 한 파일이기 때문에 Copy&Paste가 부담 없다. • 스탠드얼론 AI 스크립트를 여러 종 둘 수 있다. • 특이한 구현이 일반적인 구현을 더럽히지 않는다. • 특히 보스 AI의 구현이 완전히 격리된다.
  44. 44. 매뉴얼자동생성 • 바인딩 정보는 이미 다 가지고 있다. • 어떤 함수와 클래스가 있는지 html 파일로 출력하게 했다.
  45. 45. 실행로그 • 그 틱에서 실행한 라인을 표시한다. • 브레이크포인트보다 훨씬 편하다. • 특히 여러 AI를 동시에 관찰할 때.
  46. 46. 한글식별자 • 상당수의 함수에 주석이 필요 없었다.
  47. 47. 에디트플러스구문강조파일 • 만들기 쉽다.
  48. 48. HFSM 언어: 아쉬운 것 주로 내부 구현
  49. 49. 파서/타입체커/코드생성기가한몸 • 1-pass • Recursive Descent 파서의 재귀호출 과정에서 모든 작업을 수행 • 별 생각 없이 루아 컴파일러 구조를 모방한 것;;; 문법 바꾸거나 새로운 거 추가할 때 고생했다. 파스 트리를 먼저 생성하고 나서, 그 파스 트리를 해석해서 타입체크하고 바이트코드를 생성하는 것이 바람직할 듯.
  50. 50. 정수형이없다 • 값으로 취급하는 내장 타입들: number, bool, string • HFSM number = C++ float • 이것도 별 생각 없이 루아 모방… C++ 코드에서 매번 캐스팅하기 은근히 귀찮다.
  51. 51. 사용자정의타입은힙에만넣을수있다 • 스크립트에 바인딩하는 클래스는 ReferenceCounter 상속을 강제했다. • 별 생각 없이 자바 모방 아주 간단한 객체에도 스마트 포인터를 써야 하는 부담이 있다.
  52. 52. 레지스터머신 • number/bool, string, object의 레지스터 3종 세트 • 전역변수가 점유하고 남는 레지스터 공간을 지역변수가 사용 스크립트 언어 안에서의 함수호출을 만들 수가 없네?; 레지스터 공간 추적하기도 엄청 까다롭네?; 스테이트 사이사이에 전역변수를 추가할 수가 없네?; 1-pass 컴파일러다 보니…
  53. 53. 제네릭리스트구현 • ‘객체의 리스트’ 문법을 내장. • 리스트 구현은 한 벌만 짜고, 컴파일러 트릭으로 구현을 공유했다. • 문법은 파이썬, 구현은 자바를 모방 역시 별 생각이 없었다. 귀찮은 문제가 잡다하게 발생;; 타입 체크가 아주 복잡하다던가 리스트를 객체의 멤버로 넣을 수가 없다던가 내장 타입의 리스트를 만들 수가 없다던가 (더러운 박싱 언박싱…) var 일반공격1 = AttackType() { … }; var 일반공격2 = AttackType() { … }; var 일반공격목록 = [ 일반공격1, 일반공격2 ];
  54. 54. 바인딩 • 템플릿+매크로 서커스 역시 별 생각이 없었다. 익숙한 대로 했을 뿐… • 읽기도 고치기도 어렵다. • 노출한 함수를 C++에서 호출하기 어렵다. 특수한 주석으로 추가하고, 코드생성 하는 게 낫지 않을까? (NDC’11 코드 생성을 사용해 개발 속도 높이기 / 김이선님 발표)
  55. 55. 다시만든다면 • 스마트 포인터를 언어의 핵심에서 제거하고, • 모든 타입을 값으로 취급한다. The STL Way! • 스크립트 스택 메모리에 직접 객체를 할당하고 관리. • 제네릭도 제거한다. List<A>와 List<B>는 전혀 다른 타입으로 취급. • 레퍼런스 시맨틱을 쓰고 싶으면 스마트 포인터를 직접 바인딩하게 한다. C++와 심리스하게 붙는 스태틱 타입 스크립트 언어를 만들려면, 이게 가장 심플한 답이 아닐까…
  56. 56. 요약 & 결론
  57. 57. 코드핫로딩 • 실행 중 DLL 교체! • 간단히 적용 가능. DLL 빌드 대응이 되어 있지 않으면 좀 힘들겠지만…
  58. 58. 자체제작스크립트언어 • 잘 쓰고 있다. • 의외로 만들기 어렵지 않았다. • 더 넓은 범위에서 써도 되지 않을까! • 내부 구현이 좀 아쉽다. 생각없이 베끼다 보니까…
  59. 59. 게임 스크립트 언어, 만족하십니까?
  60. 60. Q/A
  61. 61. 명령어구조 • 컴파일하면 코드 뭉치와 각종 테이블이 나온다. 컴파일을 먼저 해서 배포하는 것을 염두에 둠. • 1 명령어 = 32비트. 19종류. 레지스터는 9비트. • 명령어의 맥락을 보고 3종류의 레지스터 중 어느 것을 쓸지 알 수 있다. 모르는 경우에는 명렁어 내부에 힌트를 인코딩해 넣어서 해결. • 함수호출이나 필드 액세스의 경우 별도의 테이블이 있고 명령어에는 테이블 인덱스만 저장. – 예) 함수호출: this 레지스터, 인자가 담긴 레지스터들, 리턴값을 담을 레지스터, 호출 직후에 yield할까 • 스트링/숫자 리터럴도 별도의 테이블에 넣고 명령어에는 테이블 인덱스만 저장.

Transcript

  1. 1. C++에서 극한의 생산성 뽑아내기 M2 AI코드 개발 생산성 향상 사례 코드 핫로딩과 자체 제작 스크립트 언어 넥슨코리아 W엔지니어링팀 이승재
  2. 2. 카바티나 스토리 2007~2009 데스크탑 히어로즈 2010~2011 마비노기2 2011~ 이승재 프로그래머
  3. 3. 어젠다 1. 런타임에 코드를 교체하는 기법을 소개하고, 2. C++에 찰싹 달라붙는 스크립트 언어를 만들어본 경험을 공유합니다.
  4. 4. 작년가을 “AI를 맡으세요” “헉” 미션: 기존 코드 튜닝, AI 패턴 추가
  5. 5. AI 작업의특징 • 외부 코드에 많이 의존한다. • 시간의 흐름에 따라 연속적으로 변화한다. • 자연스러운 행동이란 뭐지? • 군집 행동의 테스트 케이스는 어떻게 하지? 유닛 테스트는 어렵겠고… 이터레이션 속도 = 생산성!
  6. 6. 이터레이션 • 코드 수정 • 빌드 • 로딩 • 테스트 이 루프가 1분 이상;;
  7. 7. 일반적인해결책들 • 컴파일 시간: PCH, 의존성 감소 • 링크 시간: DLL 빌드 • 로딩 시간: 로딩 최적화 이미 잘 적용되어 있고 내가 손대기 어려운 것들…
  8. 8. 이래선, 안되겠어. 빨리 어떻게든 하지 않으면 ….
  9. 9. 1. 실행 중 DLL 교체 2. 스크립트 언어 자체 제작
  10. 10. 10:41:13 김주복 [EIAS] 간단히 말하면 플러그인이잖......
  11. 11. 플러그인? • Dynamic Linked Library (DLL) – 실행 시점에 코드를 메모리에 로드한다 • DLL 링크 방법 – Implicit Link (묵시적 링크) – Explicit Link (명시적 링크)
  12. 12. ImplicitLink • 보통 접하는 DLL 빌드__declspec(dllimport) • 라이브러리 프로젝트 빌드하면 .lib와 .dll 생성됨 • 사용하는 쪽에선 .lib을 스태틱 링크한다. • .lib이 초기화될 때 .dll을 찾아서 자동으로 로드한다. – DLL의 생명주기를 통제할 수 없다. – 언로드하거나 교체할 수 없다.
  13. 13. ExplicitLink • HMODULE WINAPI LoadLibrary( LPCTSTR lpFileName); // DLL을 로드한다. • typedef int (FAR WINAPI *FARPROC)(); • FARPROC WINAPI GetProcAddress( HMODULE hModule, LPCSTR lpProcName); // 함수 주소를 얻는다. 원리는 정말 간단
  14. 14. 문제와 해결책들
  15. 15. 진입점문제 extern “C” 로 묶어주지 않으면 이름이 바뀐다!;;네임 맹글링;; 노출하는 함수가 많으면 피곤하다. 일일이 GetProcAddress 해야 하므로.. 함수 시그니처 어긋남을 컴파일러가 감지하지 못한다.
  16. 16. 진입점문제:해결책 • 인터페이스 클래스를 라이브러리와 호출부가 공통 참조하고, 이것을 상속·구현한 객체를 라이브러리 함수가 리턴하게 했다. • GetProcAddress 로 단 하나의 함수만 불러오면 된다. • COM의 QueryInterface와도 비슷하다.
  17. 17. 외부심볼참조문제 • Explicit Link DLL은 EXE의 심볼을 볼 수 없다. • 프로그램의 다른 부분이 Implicit Link DLL로 되어 있다면 추가 비용 없이 그대로 사용 가능하다. • 아니라면 좀 어려운 문제.
  18. 18. DLL안전하게해제 DLL 해제하기 전에, 내부로의 연결을 모두 끊어야 한다. 빠뜨리면 크래시! → DLL을 해제하지 않게 해서 해결. 코드 변경할 때마다 계속 중복 로드하게 함
  19. 19. DLL덮어쓰기 로드된 DLL은 덮어쓰기 불가능.. 빌드가 되지 않는다! → 로드 전에 DLL 파일 이름을 랜덤하게 바꾸어서 해결 이름이 비슷한 DLL 파일이 계속 쌓인다! → 클라이언트 처음 뜰 때 검색해서 삭제
  20. 20. 빌드구성 • 릴리스 버전에는 DLL 교체가 필요 없다. • 스태틱 링크가 가능할까?
  21. 21. 빌드구성 디버그 계열 빌드 릴리스 계열 빌드 라이브러리의 출력 형태 DLL 정적 라이브러리 사용하는 프로젝트에 추가 종속성 설정 안 함 링크될 .lib 이름 EXPLICIT_LOAD_LIBRARY(“AI.dll”) LoadLibrary(“AI.dll”) 사라짐 EXPLICIT_GET_PROC_ADDRESS( h, FuncType, Func) (FuncType)GetProcAddress( h, “Func”) &Func ※ 공통: 사용하는 프로젝트에서 참조 프로젝트로 라이브러리 프로젝트를 추가하되, 라이브러리 종속성은 끈다. 빌드 순서는 유지하되 Implicit link는 막기 위함
  22. 22. 행복해졌습니다 한동안은..
  23. 23. 1. 실행 중 DLL 교체 2. 스크립트 언어 자체 제작
  24. 24. 기존AI코드 • C++, 스테이트 패턴 • AI 종류별 설정 파일이 존재 거대한 고정 파이프라인C++이 있고 이것을 제어하는 스위치들데이터이 잔뜩 붙어있는 형태
  25. 25. 기존코드의문제 • 복잡도가 아주 빠르게 증가한다. – 기존 코드를 최대한 활용하고, 필요한 곳에서 분기하도록 만들게 되는 경향이 있다. – 보스는 전용 기능 덩어리.. • 디버깅하기 어렵다. – 이 설정 항목의 의미는 뭐지? – 얘는 왜 이렇게 행동하지?
  26. 26. 대안: 스크립트로이식하자 • 거대 고정 파이프라인을 해체하자. – 예) 기존: ‘공격중’ 스테이트를 많은 AI종류가 공유 대안: 각 AI 스크립트에 ‘공격중’ 스테이트를 둔다 • 중복이 있더라도 부담 없도록, 표현이 간결해야 한다. • 비슷한 유형은 묶어서 표현할 수 있도록 하자.
  27. 27. 선택지 • Lua • 다른 게임 스크립팅 언어들 • 자체 제작
  28. 28. 선택지: Lua • 장점: – 널리 알려져 있다. – 나도 익숙하다. • 단점: – FSM을 원하는 만큼 간결하게 표현 못한다. – 단순 실수를 컴파일 타임에 잡지 못한다! NDC’11 <온라인 게임 처음부터 끝까지 동적언어로 만들기> 참고.
  29. 29. 선택지: 다른게임스크립팅언어들 • AngelScript, Squirrel, GameMonkey, … • 잘 몰라서 탈락. 어떤 특성이 있는지, 어떤 함정이 있는지…
  30. 30. 선택지: 자체제작 • 할 수 있을까? • 정말 좋을까? • 비용은 얼마나 들까?
  31. 31. 할수있을까? Lua 5.0 구현 논문과, Lua로 된 Lua 파서 코드를 봤었다. 딱히 외계인 기술이 아니다, 도전해 볼만한 일이라고 판단.
  32. 32. 정말좋을까? • 전에 해 봤다: FSM 언어 – 카바티나 스토리 – 캐릭터 조작계와 AI에 사용 – 간단한 구조 변수와 Expression 없음, 한정된 형태의 제어문 – 적은 투자로 큰 효과를 봤던 기억 [시작] 전투중이면 -> 가만서있는다=0.5, [전투중] !다돌아왔으면 -> 제자리로돌아간다, [돌아가기] 무조건 -> 배회한다=$배회주기 [전투중] !전투중이면 -> 제자리로돌아간다, [돌아가기] 스킬사용해본다( $스킬_평타 ) -> 시전성공 무조건 -> 가만서있는다=0.1 [돌아가기] 전투중이면 -> 가만서있는다=0.5, [전투중] 다돌아왔으면 -> 가만서있는다=0.5, [시작] 무조건 -> 제자리로돌아간다
  33. 33. 비용은얼마나들까? • 개인 시간에 proof-of-concept을 시도해 보았다. • if문과 변수 선언을 갖는 언어를 구현하는 데 며칠 걸림. 할 수 있겠다!
  34. 34. HFSMHierarchicalFiniteStateMachine 스테이트 머신을 위한 문법 실행 중 일시 정지 한글 식별자 멤버 변수 초기화 구문 강조
  35. 35. HFSM 언어: 잘 한 것 주로 언어의 모양과 기능
  36. 36. 스테이트머신을표현하는문법 • [State] • # Update { … } • # Enter { … }, # Leave { … } 실제로는 거의 사용하지 않았다. • if (…) { goto [State]; }
  37. 37. 실행중일시정지 • yield; • 홀드(30)~; • 공격준비동작실행(…)~; (일종의) 코루틴. 함수호출이 없으므로 중간 상태 보관 구현이 아주 간단했다. 잘 쓰면 스테이트 개수를 아주 많이 줄일 수 있다.
  38. 38. 컴파일타임타입체크 • 모든 문Statement의 유효성과 식Expression의 타입을 컴파일 타임에 체크한다. – 컴파일 타임 = 스크립트 로드 시점 – 바인딩 정보를 활용한다. 일단 로딩에 성공하면 스크립트 실행중 에러가 나지 않는다. 격렬한 리팩토링도 무섭지 않다.
  39. 39. 사용자정의타입 • 클래스의 바인딩을 C++ 코드로 작성한다. • 스크립트에서 바로 멤버 변수를 읽고 쓸 수 있다. • 스크립트에서 바로 멤버 함수를 호출할 수 있다. 스크립트에서 직접 클래스를 선언하는 문법을 만들지 않았다.
  40. 40. 멤버변수초기화 var 탈것서브어택 = AttackType() { 준비시간 = MinMax(40, 45), 공격액션 = "SubAttack" }; var 탈것서브어택 = AttackType(); 탈것서브어택.준비시간 = MinMax(40, 45); 탈것서브어택.공격액션 = "SubAttack"; • C#을 모방. • 복잡한 데이터 서술에 좋다. • 일반적인 데이터 로딩에도 활용할 수 있을 것 같다. =
  41. 41. 파생스크립트 • 비슷한 AI들이 많다. • 다 따로 만들면 관리하기 어렵다. 아직 코드가 굳어지기 전에는 더욱. • 상속 같은 걸 끼얹나?
  42. 42. 파생스크립트 • 스탠드얼론 AI 스크립트 – 모든 기능을 사용할 수 있다. – 행동의 절차를 서술한다. • 확장 스크립트 – extend XXXX; 로 시작한다. – Init 전역 함수만 만들 수 있다. – 수정이 필요한 설정이나 전역변수를 Init에서 변경한다.
  43. 43. 파생스크립트 “예전 구조랑 똑같지 않나요?” • 한 AI = 한 파일이기 때문에 Copy&Paste가 부담 없다. • 스탠드얼론 AI 스크립트를 여러 종 둘 수 있다. • 특이한 구현이 일반적인 구현을 더럽히지 않는다. • 특히 보스 AI의 구현이 완전히 격리된다.
  44. 44. 매뉴얼자동생성 • 바인딩 정보는 이미 다 가지고 있다. • 어떤 함수와 클래스가 있는지 html 파일로 출력하게 했다.
  45. 45. 실행로그 • 그 틱에서 실행한 라인을 표시한다. • 브레이크포인트보다 훨씬 편하다. • 특히 여러 AI를 동시에 관찰할 때.
  46. 46. 한글식별자 • 상당수의 함수에 주석이 필요 없었다.
  47. 47. 에디트플러스구문강조파일 • 만들기 쉽다.
  48. 48. HFSM 언어: 아쉬운 것 주로 내부 구현
  49. 49. 파서/타입체커/코드생성기가한몸 • 1-pass • Recursive Descent 파서의 재귀호출 과정에서 모든 작업을 수행 • 별 생각 없이 루아 컴파일러 구조를 모방한 것;;; 문법 바꾸거나 새로운 거 추가할 때 고생했다. 파스 트리를 먼저 생성하고 나서, 그 파스 트리를 해석해서 타입체크하고 바이트코드를 생성하는 것이 바람직할 듯.
  50. 50. 정수형이없다 • 값으로 취급하는 내장 타입들: number, bool, string • HFSM number = C++ float • 이것도 별 생각 없이 루아 모방… C++ 코드에서 매번 캐스팅하기 은근히 귀찮다.
  51. 51. 사용자정의타입은힙에만넣을수있다 • 스크립트에 바인딩하는 클래스는 ReferenceCounter 상속을 강제했다. • 별 생각 없이 자바 모방 아주 간단한 객체에도 스마트 포인터를 써야 하는 부담이 있다.
  52. 52. 레지스터머신 • number/bool, string, object의 레지스터 3종 세트 • 전역변수가 점유하고 남는 레지스터 공간을 지역변수가 사용 스크립트 언어 안에서의 함수호출을 만들 수가 없네?; 레지스터 공간 추적하기도 엄청 까다롭네?; 스테이트 사이사이에 전역변수를 추가할 수가 없네?; 1-pass 컴파일러다 보니…
  53. 53. 제네릭리스트구현 • ‘객체의 리스트’ 문법을 내장. • 리스트 구현은 한 벌만 짜고, 컴파일러 트릭으로 구현을 공유했다. • 문법은 파이썬, 구현은 자바를 모방 역시 별 생각이 없었다. 귀찮은 문제가 잡다하게 발생;; 타입 체크가 아주 복잡하다던가 리스트를 객체의 멤버로 넣을 수가 없다던가 내장 타입의 리스트를 만들 수가 없다던가 (더러운 박싱 언박싱…) var 일반공격1 = AttackType() { … }; var 일반공격2 = AttackType() { … }; var 일반공격목록 = [ 일반공격1, 일반공격2 ];
  54. 54. 바인딩 • 템플릿+매크로 서커스 역시 별 생각이 없었다. 익숙한 대로 했을 뿐… • 읽기도 고치기도 어렵다. • 노출한 함수를 C++에서 호출하기 어렵다. 특수한 주석으로 추가하고, 코드생성 하는 게 낫지 않을까? (NDC’11 코드 생성을 사용해 개발 속도 높이기 / 김이선님 발표)
  55. 55. 다시만든다면 • 스마트 포인터를 언어의 핵심에서 제거하고, • 모든 타입을 값으로 취급한다. The STL Way! • 스크립트 스택 메모리에 직접 객체를 할당하고 관리. • 제네릭도 제거한다. List<A>와 List<B>는 전혀 다른 타입으로 취급. • 레퍼런스 시맨틱을 쓰고 싶으면 스마트 포인터를 직접 바인딩하게 한다. C++와 심리스하게 붙는 스태틱 타입 스크립트 언어를 만들려면, 이게 가장 심플한 답이 아닐까…
  56. 56. 요약 & 결론
  57. 57. 코드핫로딩 • 실행 중 DLL 교체! • 간단히 적용 가능. DLL 빌드 대응이 되어 있지 않으면 좀 힘들겠지만…
  58. 58. 자체제작스크립트언어 • 잘 쓰고 있다. • 의외로 만들기 어렵지 않았다. • 더 넓은 범위에서 써도 되지 않을까! • 내부 구현이 좀 아쉽다. 생각없이 베끼다 보니까…
  59. 59. 게임 스크립트 언어, 만족하십니까?
  60. 60. Q/A
  61. 61. 명령어구조 • 컴파일하면 코드 뭉치와 각종 테이블이 나온다. 컴파일을 먼저 해서 배포하는 것을 염두에 둠. • 1 명령어 = 32비트. 19종류. 레지스터는 9비트. • 명령어의 맥락을 보고 3종류의 레지스터 중 어느 것을 쓸지 알 수 있다. 모르는 경우에는 명렁어 내부에 힌트를 인코딩해 넣어서 해결. • 함수호출이나 필드 액세스의 경우 별도의 테이블이 있고 명령어에는 테이블 인덱스만 저장. – 예) 함수호출: this 레지스터, 인자가 담긴 레지스터들, 리턴값을 담을 레지스터, 호출 직후에 yield할까 • 스트링/숫자 리터럴도 별도의 테이블에 넣고 명령어에는 테이블 인덱스만 저장.

More Related Content

More from devCAT Studio, NEXON

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

×