11. 플러그인?
• Dynamic Linked Library (DLL)
– 실행 시점에 코드를 메모리에 로드한다
• DLL 링크 방법
– Implicit Link (묵시적 링크)
– Explicit Link (명시적 링크)
12. ImplicitLink
• 보통 접하는 DLL 빌드__declspec(dllimport)
• 라이브러리 프로젝트 빌드하면 .lib와 .dll 생성됨
• 사용하는 쪽에선 .lib을 스태틱 링크한다.
• .lib이 초기화될 때 .dll을 찾아서 자동으로 로드한다.
– DLL의 생명주기를 통제할 수 없다.
– 언로드하거나 교체할 수 없다.
13. ExplicitLink
• HMODULE WINAPI LoadLibrary(
LPCTSTR lpFileName); // DLL을 로드한다.
• typedef int (FAR WINAPI *FARPROC)();
• FARPROC WINAPI GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName); // 함수 주소를 얻는다.
원리는 정말 간단
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는 막기 위함
24. 기존AI코드
• C++, 스테이트 패턴
• AI 종류별 설정 파일이 존재
거대한 고정 파이프라인C++이 있고
이것을 제어하는 스위치들데이터이 잔뜩 붙어있는 형태
25. 기존코드의문제
• 복잡도가 아주 빠르게 증가한다.
– 기존 코드를 최대한 활용하고,
필요한 곳에서 분기하도록 만들게 되는 경향이 있다.
– 보스는 전용 기능 덩어리..
• 디버깅하기 어렵다.
– 이 설정 항목의 의미는 뭐지?
– 얘는 왜 이렇게 행동하지?
26. 대안: 스크립트로이식하자
• 거대 고정 파이프라인을 해체하자.
– 예) 기존: ‘공격중’ 스테이트를 많은 AI종류가 공유
대안: 각 AI 스크립트에 ‘공격중’ 스테이트를 둔다
• 중복이 있더라도 부담 없도록, 표현이 간결해야 한다.
• 비슷한 유형은 묶어서 표현할 수 있도록 하자.
31. 할수있을까?
Lua 5.0 구현 논문과, Lua로 된 Lua 파서 코드를 봤었다.
딱히 외계인 기술이 아니다, 도전해 볼만한 일이라고 판단.
32. 정말좋을까?
• 전에 해 봤다: FSM 언어
– 카바티나 스토리
– 캐릭터 조작계와 AI에 사용
– 간단한 구조
변수와 Expression 없음, 한정된 형태의 제어문
– 적은 투자로 큰 효과를 봤던 기억
[시작]
전투중이면 -> 가만서있는다=0.5, [전투중]
!다돌아왔으면 -> 제자리로돌아간다, [돌아가기]
무조건 -> 배회한다=$배회주기
[전투중]
!전투중이면 -> 제자리로돌아간다, [돌아가기]
스킬사용해본다( $스킬_평타 ) -> 시전성공
무조건 -> 가만서있는다=0.1
[돌아가기]
전투중이면 -> 가만서있는다=0.5, [전투중]
다돌아왔으면 -> 가만서있는다=0.5, [시작]
무조건 -> 제자리로돌아간다
33. 비용은얼마나들까?
• 개인 시간에 proof-of-concept을 시도해 보았다.
• if문과 변수 선언을 갖는 언어를 구현하는 데 며칠 걸림.
할 수 있겠다!
36. 스테이트머신을표현하는문법
• [State]
• # Update { … }
• # Enter { … }, # Leave { … } 실제로는 거의 사용하지 않았다.
• if (…) { goto [State]; }
37. 실행중일시정지
• yield;
• 홀드(30)~;
• 공격준비동작실행(…)~;
(일종의) 코루틴.
함수호출이 없으므로 중간 상태 보관 구현이 아주 간단했다.
잘 쓰면 스테이트 개수를 아주 많이 줄일 수 있다.
38. 컴파일타임타입체크
• 모든 문Statement의 유효성과 식Expression의 타입을
컴파일 타임에 체크한다.
– 컴파일 타임 = 스크립트 로드 시점
– 바인딩 정보를 활용한다.
일단 로딩에 성공하면 스크립트 실행중 에러가 나지 않는다.
격렬한 리팩토링도 무섭지 않다.
39. 사용자정의타입
• 클래스의 바인딩을 C++ 코드로 작성한다.
• 스크립트에서 바로 멤버 변수를 읽고 쓸 수 있다.
• 스크립트에서 바로 멤버 함수를 호출할 수 있다.
스크립트에서 직접 클래스를 선언하는 문법을 만들지 않았다.
40. 멤버변수초기화
var 탈것서브어택 = AttackType() {
준비시간 = MinMax(40, 45),
공격액션 = "SubAttack"
};
var 탈것서브어택 = AttackType();
탈것서브어택.준비시간 = MinMax(40, 45);
탈것서브어택.공격액션 = "SubAttack";
• C#을 모방.
• 복잡한 데이터 서술에 좋다.
• 일반적인 데이터 로딩에도
활용할 수 있을 것 같다.
=
41. 파생스크립트
• 비슷한 AI들이 많다.
• 다 따로 만들면 관리하기 어렵다.
아직 코드가 굳어지기 전에는 더욱.
• 상속 같은 걸 끼얹나?
42. 파생스크립트
• 스탠드얼론 AI 스크립트
– 모든 기능을 사용할 수 있다.
– 행동의 절차를 서술한다.
• 확장 스크립트
– extend XXXX; 로 시작한다.
– Init 전역 함수만 만들 수 있다.
– 수정이 필요한 설정이나 전역변수를
Init에서 변경한다.
43. 파생스크립트
“예전 구조랑 똑같지 않나요?”
• 한 AI = 한 파일이기 때문에 Copy&Paste가 부담 없다.
• 스탠드얼론 AI 스크립트를 여러 종 둘 수 있다.
• 특이한 구현이 일반적인 구현을 더럽히지 않는다.
• 특히 보스 AI의 구현이 완전히 격리된다.
49. 파서/타입체커/코드생성기가한몸
• 1-pass
• Recursive Descent 파서의 재귀호출 과정에서 모든 작업을 수행
• 별 생각 없이 루아 컴파일러 구조를 모방한 것;;;
문법 바꾸거나 새로운 거 추가할 때 고생했다.
파스 트리를 먼저 생성하고 나서,
그 파스 트리를 해석해서 타입체크하고 바이트코드를 생성하는 것이 바람직할 듯.
50. 정수형이없다
• 값으로 취급하는 내장 타입들: number, bool, string
• HFSM number = C++ float
• 이것도 별 생각 없이 루아 모방…
C++ 코드에서 매번 캐스팅하기 은근히 귀찮다.
52. 레지스터머신
• number/bool, string, object의 레지스터 3종 세트
• 전역변수가 점유하고 남는 레지스터 공간을 지역변수가 사용
스크립트 언어 안에서의 함수호출을 만들 수가 없네?;
레지스터 공간 추적하기도 엄청 까다롭네?;
스테이트 사이사이에 전역변수를 추가할 수가 없네?;
1-pass 컴파일러다 보니…
53. 제네릭리스트구현
• ‘객체의 리스트’ 문법을 내장.
• 리스트 구현은 한 벌만 짜고, 컴파일러 트릭으로 구현을 공유했다.
• 문법은 파이썬, 구현은 자바를 모방 역시 별 생각이 없었다.
귀찮은 문제가 잡다하게 발생;;
타입 체크가 아주 복잡하다던가
리스트를 객체의 멤버로 넣을 수가 없다던가
내장 타입의 리스트를 만들 수가 없다던가 (더러운 박싱 언박싱…)
var 일반공격1 = AttackType() { … };
var 일반공격2 = AttackType() { … };
var 일반공격목록 = [ 일반공격1, 일반공격2 ];
54. 바인딩
• 템플릿+매크로 서커스
역시 별 생각이 없었다. 익숙한 대로 했을 뿐…
• 읽기도 고치기도 어렵다.
• 노출한 함수를 C++에서 호출하기 어렵다.
특수한 주석으로 추가하고, 코드생성 하는 게 낫지 않을까?
(NDC’11 코드 생성을 사용해 개발 속도 높이기 / 김이선님 발표)
55. 다시만든다면
• 스마트 포인터를 언어의 핵심에서 제거하고,
• 모든 타입을 값으로 취급한다. The STL Way!
• 스크립트 스택 메모리에 직접 객체를 할당하고 관리.
• 제네릭도 제거한다. List<A>와 List<B>는 전혀 다른 타입으로 취급.
• 레퍼런스 시맨틱을 쓰고 싶으면 스마트 포인터를 직접 바인딩하게 한다.
C++와 심리스하게 붙는 스태틱 타입 스크립트 언어를 만들려면, 이게 가장 심플한 답이 아닐까…
62. 명령어구조
• 컴파일하면 코드 뭉치와 각종 테이블이 나온다. 컴파일을 먼저 해서 배포하는 것을 염두에 둠.
• 1 명령어 = 32비트. 19종류. 레지스터는 9비트.
• 명령어의 맥락을 보고 3종류의 레지스터 중 어느 것을 쓸지 알 수 있다.
모르는 경우에는 명렁어 내부에 힌트를 인코딩해 넣어서 해결.
• 함수호출이나 필드 액세스의 경우 별도의 테이블이 있고 명령어에는 테이블 인덱스만 저장.
– 예) 함수호출: this 레지스터, 인자가 담긴 레지스터들, 리턴값을 담을 레지스터, 호출 직후에 yield할까
• 스트링/숫자 리터럴도 별도의 테이블에 넣고 명령어에는 테이블 인덱스만 저장.