GEMS 6권 4.6 게임 객체 구성요소 시스템

GEMS 2016. 2. 23. 15:37

가장 일반적인 수준에서 게임 객체는

객체의 위치와 뱡향을 나타내는 변환 행렬과 고유한 식별자,

객체의 기본속성을 정의하는 상태정보,

그러한 상태를 수정, 조회하는 메서드들로 구성된다.

posted by 알쿠미

GEMS 5권 템플릿을 이용한 C++ 반영 기능 구현

GEMS 2016. 2. 16. 17:43

사용자 친화적 반영 시스템을 만드는데 필요한 요구사항


효율성 : 제한된 메모리를 가진 콘솔 게임기에서도 잘 돌아가야한다.


크로스 플랫폼 :


투명성 : 프로그래머가 자신의 코딩 방식을 바꾸지 않고도 반영 기능을 사용할 수 있어야 한다. 

프로그래머의 코드와 연결시키는 책임은 프로그래머가 아니라 시스템이 져야한다.


컴파일에 영향을 주지 말아야 한다. :


유연성 : 프로그래머는 클래스 인터페이스에서 외부에 노출될 부분을 구체적으로 제어할 수 있어야한다.

(종종 클래스 전체가 아니라 클래스의 제한된 기능만을 노출하고 싶을 때가 있기 때문이다.


견고함 : 시스템은 형식에 안전해야 하며 흔한 실수도 모두 잡아낼 수 있어야 한다.

(템플릿 사용의 좋은 근거)


반영 시스템의 3가지 부분

등록 : 프로그래머가 클래스의 어떤 자료 멤버들을 외부세계에 노출할 것인지 명시하는 것


조사 : 클래스가 지원하는 속성과 메서드 이름, 그리고 그 형식을 프로그래머가 조사할 수 있게 한다.


조작 : 외부 인터페이스(스크립트, GUI, 파일 등)로부터 프로그래머의 코드를 호출할 수 있게 한다.


1부 : 실행시점 형식 정보

이 RTTI에 쓰이는 형식 자료구조

* 클래스 이름 - 하나의 문자열에 저장되는, 꾸미지 않은 클래스 이름이다.

* 클래스를 식별하는 고유한 클래스 ID - 사용자가 제공한, 고유한 32비트 정수. 클래스 형식을 빠르게 비교하는데에나 파일이나 네트웍으로 클래식 형식 정보를 직렬화하는 데에는 숫자가 더 좋다.

* 조상의 RTTI 정보를 가리키는 포인터 - 조상 RTTI에 대한 포인터는 이름 그대로이다.

* 팩토리 함수에 대한 함수 콜백 - 이 클래스 형식의 새 객체 인스턴스를 생성하는 팩토리 함수를 가리킨다. 실행시점에서 다른 것은 전혀 모르고 클래스 ID만 알고 있는 상황에서 객체를 생성할 때 매우 유용하다.



posted by 알쿠미

GEMS 5권 구성요소 기반 객체 관리

GEMS 2016. 2. 16. 17:31


posted by 알쿠미

GEMS 4권 클럭 : 게임의 심장 박동

GEMS 2016. 2. 6. 20:26

게임에서 클럭(clock)은 모든 것을 주도한다.

시간을 다룰 때 그냥 필요할 때마다 시스템 타이머로부터 시가을 얻는 방식을 사용하기도 하는데, 그런 방법에는 몇 가지 문제점들이 있다.

* 한 프레임 안의 여러 지점들에서 시간을 조회하면 그 때마다 다른 값이 반환될 수 있으며, 그러면 한 화면의 물체들을 갱신할 때 물체가 이상하게 워프하는 모습이 나타나기도 한다.


* 게임을 일시중지하되 특정 부분만 계속 실행되게 할 때 문제가 있을 수 있다.


* 프레임률 변동 때문에 애니메이션이나 카메라 이동이 눈에 띄게 떨리는 현상이 나타날 수 있다.


시간의 기초

한 프레임 안에서 일어나는 주요한 사건들

* 사용자의 입력을 점검

* AI 처리

* 네트워크 패킷처리

* 게임 세계의 물체들을 갱신

* 보여줘야 할 것들을 화면에 표시


게임이 시간을 다루는 방식은 크게 두 가지이다. 하나는 가변 프레임 간격이도 또 하나는 고정 프레임 간격이다. PC용 게임들은 대부분 가변 프레임 간격을 사용한다.

가변 프레임 간격이란 한 프레임에 걸리는 시간이 그 프레임에서 화면에 그리는 것들에 따라 다를 수 있다는 뜻이다.

고정 프레임 간격은 주로 콘솔 게임들에서 볼 수 있다.

콘솔 게임의 경우에는 실행 하드웨어를 미리 파악할 수 있으며, 따라서 한 프레임에 걸리는 시간을 미리 결정해서 고정시켜 둘 수 있다.


클럭 시스템 조직화

* 현재 시간과 한 프레임의 지속 시간을 신뢰성 있게 제공한다.

* 프로그램의 다른 부분과는 독립적으로 게임플레이 시간을 정지하거나 비례한다.

* 시간을 초기값으로 되돌리거나 임의의 값으로 설정할 수 있다.

* 가변 프레임 간격과 고정 프레임 모두를 지원한다.

* 간격 차이가 큰 일련의 프레임들 때문에 생기는 결함을 방지한다.

* 정밀도 문제를 방지한다.


클럭이라는 개념과 타이머라는 개념을 구분할 필요가 있다.

클럭은 게임 안에서 단 하나만 존재하며, 타이머는 여러 개 존재할 수 있다.

클럭은 현재 시간을 보고하며, 항상 증가하지만 한다.

클럭을 중지시키거나 조작할 수 없다.


타이머는 게임 내의 개별적인 용도에 따라 여러개를 만들어서 사용할 수 있다.

타이머는 기본적으로 클럭에 의해 작동하며, 사용자가 직업 조작하는 것이 가능하다.


클럭은 시간을 어디서 얻을까?


스파이크 - 프레임 하나가 시간이 갑자기 길어지는것?

이럴경우 프레임의 상한시간을 둔다.


수직 동기 - 다음 프레임이 그려질 준비가 다 됬다는건가?

다음번의 수직 회귀를 기다렸다가 다음 프레임을 그리는 방법이 흔히 쓰인다. 그런 방법에서는 한 프레임에 필요한 모든것을 수행하고, 수직 동기를 기다리고, 수직 동기가 오면 준비된 후면 버퍼를 표시한다.

오늘날의 PC에선 비효율적이다.(병렬적인 그래픽 시스템에선)


정밀도 - 


posted by 알쿠미

GEMS 4권 디버깅의 과학

GEMS 2016. 2. 5. 22:12

5단계 디버깅 공정

단계 1 : 문제를 일관되게 재현한다.

버그가 나오는 환경과 상황을 구체적으로 알수 있는 것이 중요하다.

그래야지 버그를 재현할 수 있기 때문에


단계 2 : 단서 수집

단서는 가능한 원인들 중 가망이 없는 것들을 제거하는 역할을 한다.

버그와 관계없는 코드들을 않보게 하는 것이 중요하다?


단계 3 : 오류 지적

두가지 방법이 있다.

1. 버그에 원인에 대한 가설을 만들고, 그 가설을 증명 또는 반증하는 것

2. 분할정복

실패의 지점을 식별하고, 거기서부터 입력들을 통해 오류의 근원으로 역추적해 들어가는 것


단계 4 : 문제 고치기

버그는 버그가 있는 코드를 작성한 프로그래머가 고치는게 좋지만 그럴 수 없을 경우에는 작성한 프로그래머에게 정확히 설명을 듣고 맥락을 확인한 후에 해야한다.


단계 5 : 해결책의 검사

이 버그 정말로 수정되었는지 ? 이 버그로 인해 다른 버그가 생겨나지는 않았는지? 확인해야한다.


디버깅 요령들

- 가정을 의심한다.


- 상호작용과 간섭을 최소화한다.


- 무작위성을 최소화한다.


- 복잡한 계산을 여러단계로 나눈다.

수많은 계산이 한줄에 있다면 디버깅하기 힘들다.


- 경계 조건들을 점검한다.

마지막 값이나 처음값이 제대로 들어가는지 제대로 도달하는지 확인하자


- 병렬 계산을 분할한다.

스레드나 프로세스 간의 경쟁 조건이 의심스럽다면 코드를 직렬화 해서 버그를 찾아보는 것도 방법이다.


- 최근 변경된 코드를 점검한다.


- 버그를 다른 사람에게 설명한다.

다른사람에게 설명하다보면 모순이 되는 부분을 발견할 때가 있다.


- 동료와 함께 디버깅한다.

사람마다 디버깅하는 방법이 다르므로 그 다른방법으로 찾아지는 경우가 있다.


- 문제에서 잠시 벗어나라

너무 오래잡고있으면 시야가 좁아질 수도 있다.


- 외부의 도움을 요청한다.


어려운 디버깅 시나리오와 패턴들


버그가 릴리즈 빌드에서만 나타나고 디버그 빌드에서는 나타나지 않는다.

이 경우 변수를 제대로 초기화하지 않았거나 최적화된 코드에 버그가 있는 것이다.

후자에 경우에는 디버그 빌드에서 한번에 하나씩 최적화 옵션을 켜나가면서 새로 빌드하고 점검하는 것이다.

릴리즈 빌드에도 디버그 기호들을 포함시킬 수 있다.


무관해 보이는 뭔가를 수정했을 때 버그가 사라진다.

타이밍 문제이거나 메모리 덮어쓰지 문제일 가능성이 있다.

고쳐진다고 하더라도 디버깅이 끝난것이 아니므로 제대로 확인해야한다.


정말로 간헐적인 문제들

문제가 발생하였을 때 최대한 많은 정보를 기록해 두는 것이다.

문제가 언제 일어날지 모르므로 나타났을 때 최대한 많은것을 알아내자

이때 알아낸 자료들과 발생하지 않았을때의 자료와 비교해서 문제를 알아낸다.


도저히 이해할 수 없는 행동

캐시 플러싱(cache flushing) 수준을 증가시켜서 시스템을 동기화 한다.

(캐시 플러싱이 낮은 수준부터 높은수준으로의 순서임)

* 재시도

* 재빌드

* 재부팅

* 재설치


내부 컴파일러 오류

1. 완전한 재빌드를 수행한다.

2. 컴퓨터를 다시 부팅하고 완전한 재빌드를 수행한다.

3. 컴파일러가 최신 버전인지 점검한다.

4. 사용하는 라이브러리들의 버전을 점검한다.

5. 같은 코드가 다른 컴퓨터에서는 컴파일되는지 본다.


자신의 코드가 문제가 아닌 것 같다면

항상 자신의 코드를 의심해야 한다.


내부 시스템의 이해

정말로 어려운 버그를 찾기 위해서는 내부적인 시스템을 이해할 필요가 있다.


게임 프로그래머가 알아야 할 내부적인 사항은 무엇이 있을까?

* 컴파일러가 코드를 구현하는 방식을 알아야 한다.

상속, 가상함수 호출, 호출 규약, 예외 등이 어떻게 구현되는지 알고 있어야한다. 컴파일러가 메모리를 할당하고 메모리 정렬을 다루는 방법도 알 필요가 있다.


* 하드웨어 세부사항을 알아야 한다.

예를 들면 특정 하드웨어의 캐싱문제들, 주소 정렬 제약조건들, 엔디안 방식, 스택 크기, 형식의 크기들(int , log, bool 등) 등등.


* 어셈블리의 작동방식과 어셈블리 코드 읽는 법을 알아야 한다.

그러면 최적화된 빌드를 디버깅 할 때, 특히 디버거가 원래의 소스 코드를 제대로 보여주지 못할 때 많은 도움이 된다.


내부 시스템을 이애하고 작동 방식과 규칙들을 상세히 알아둘 것.


디버깅 보조를 위한 기반 추가


게임 플레이 도중 게임 변수들을 변경

실행시점에서 게임 변수들을 변경할 수 있으면 디버깅과 버그 재현이 매우 쉬워진다.


시각적인 AI 진단

텍스트와 선을 이용해서 시각적으로 AI를 확인한다.


기록기능

로그를 개별적으로 만들어 두어서 실패 원인을 추적하기 쉽게하자.


게임플레이 기록/재생 기능

버그 재현의 궁극적인 수단은 플레이어 입력을 기록하고 재생하는 것이다.

즉 특정 초기상태와 특정 플레이어 입력은 항상 동일한 결과를 내야한다.


메모리 할당의 추적

모든 할당에 대해 완전한 스택 추적을 수행할 수 있는 메모리 할당자를 갖출 것.


폭주 시 최대한 많은 정보를 출력


팀 전체를 교육

팀원들이 버그가 나타났을 때 무시하지않고 관련정보를 모으는 방법을 숙지하도록 해야하낟.


버그 방지


컴파일러의 경고 수준을 가장 높은 수준으로 설정하고, 경고를 오류로 취급하게 한다.

경고들을 최대한 교정하고, 나머지는 #pragma로 꺼버릴 것. 종종 자동적인 형변환이나 기타 경고 수준문제들이 미묘한 버그를 만들어낸다.


게임이 여러 컴파일러들에서 컴파일되게 한다.

여러 컴파일러, 플랫폼에서 작동되게 코드를 작성하다보면 코드가 견고해진다.

그리고 버그를 찾을 때 어느 플랫폼, 컴파일러에만 국한된 버그가 있을 수 있다.


독자적인 메모리 관리자를 작성한다.

콘솔게임에는 필수적인다. PC개발에 경우엔 반드시는 아니다. VC++의 메모리 시스템이 상당히 강력하고, SmartHeap같은 도구도 존재하기 때문이다.


단언문을 이용해서 가정들을 확인한다.

인수들에 대한 가정을 확인하기 위한 단언문을 추가한다. assert 매크로를 좀더 강력한 형태로 확장하는 것도 좋다.


변수를 항상 선언과 함께 초기화한다.

변수를 선언할 때 어떤 의미 있는 값을 배정할 수 없는 상황이라면 어떤 특별한 값을 배정한다.


루프와 if 문의 본문을 항상 중괄호로 감싼다.

명시적으로 감싸주는 것이 가독성이 좋다.


구분하기 힘든 변수이름들을 피한다.


동일한 코드가 여러 장소에 존재하지 않도록 한다.

코드를 변경해야 할 때 한곳만 변경하고 다른 곳은 변경하지 않을 가능성이 있기 때문이다.


마법의(하드코딩된) 수를 피한다.

코드에 숫자를 직접 사용하면 그 수치의 의미와 중요도를 쉽게 까먹을 수 있다.

마법의 수를 사용했다면 상수나 매크로를 이용해서 의미있는 이름을 붙여야 할 것이다.


테스팅할 때 코드 포괄도를 확인한다.

어떤 하나의 코드 조각을 작성했다면, 그것이 모든 분기에서 정확히 실행되는지 점검해야한다. 특정 분기에서 그 부분이 전혀 실행되지 않는 다면 버그가 들어 있을 가능성이 있다.

이런 문제는 빨리 발견할수록 좋다.







posted by 알쿠미

매크로 비법 #1 열거형을 문자열로

#연산자 - 그 뒤에 나오는 인수 주변에 따옴표를 표시할 때 사용한다.

이 방법을 이용하면 열거형 상수들의 이름을 문자열로 변환해서 로그 파일이나 화면에 출력할 수 있다.


다른 방법 하나는 헤더파일 하나에 열거형에 대한 정보를 담아두고, 그것으로부터 열거형과 문자열 이름들의 배열을 자동으로 만들어내는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// data.h
 
DATA(MSG_YouWereHit)
DATA(MSG_GameReset)
DATA(MSG_HealthRestored)
 
// data.cpp
 
#define DATA(x) x,
 
enum GameMessages
{
  #include "data.h"
};
 
#undef DATA
#define DATA(x) #x, // make enums into strings
 
static const char* GameMessageNames[] =
{
  #include "data.h"
};
 
#undef DATA
cs
이 코드만 봐서는 어떻게 동작되는지 잘 모르겠다.


매크로 비법 #2 : 이진수 형태로 된 컴파일 시점 상수

## 연산자 - 두 인수들을 하나로 결합해준다. 

ex)

#define cat(a,b) a ## b

value = cat(1,2);

전처리기에 의해 치환된 결과는 value = 12;

이를 활용하는 한가지 비법은 이진수 형태로 된 컴파일 시점 상수를 만들어내는 것이다.

매크로들이 전적으로 전처리기 안에서 처리되며 실행 시점 코드는 전혀 만들어내지 않는다는 점이다. 나중에 코드를 다시 살펴보자


매크로 비법 #3 : 표준 assert에 의미있는 메시지를 추가

assert 매크로를 메세지도 띄울 수 있도록 만들자?

ex)

#define assertmsg(a,b) assert ( a && b )

assertmsg( time > 0 , "Trigger::Set - The arg time must be > 0") ;


매크로 비법 #4 : 컴파일 시점 assert

빌드과정을 중지시켜야 하는 상황에서 사용된다.

ex) #define casset(expn) typedef char __c_ASSERT__[(expn) ?1:-1]

cassert( sizeof(MyEnum) == sizeof(unsigned int) );

cassert에 거짓인 문장이 주어지면, cassert는 크기가 음인 배열을 정의하고 빌드과정이 즉시 중지된다.


매크로 비법 #5: 배열의 원소 개수 구하기

#define NumEle(array) (sizeof(array) / sizeof((array)[0]))


매크로 비법 #6 __LINE__을 문자열로

__LINE__ // 이것이 있는 곳의 행번호를 의미하는 정수

__FILE__ // 이것이 있는 곳의 파일이름을 담은 문자열

__DATE__ // 컴파일될 때의 날짜를 담은 문자열

__TIME__ // 컴파일될 때의 시간을 담은 문자열


매크로 비법 #7 : 무한 루프 방지하기

1
2
3
4
5
6
7
8
9
10
11
12
static bool while_assert( bool a )
{
  assert( a && "while_limit: exceeded iteration limit" );
  return( a );
}
 
#define UNIQUE_VAR(x) safety_limit ## x
#define _while_limit(a,b,c) \
  assert(b>&& "while_limit: limit is zero or negative");\
  int UNIQUE_VAR(c) = b; \
  while(a && while_assert(--UNIQUE_VAR(c)>=0))
#define while_limit(a,b) _while_limit(a,b,__COUNTER__)
cs
일단은 소스만..

매크로 비법 #8 : 특화된 작은 언어
간단한 스크립트 언어같은 매크로를 만들수 있다
ex)FSM같은 것을 만드는 것도 좋다.
1
2
3
4
5
6
7
8
9
#define BeginStateMachine  if(state < 0){if(0){
#define EndStateMachine    return(true);}}else{assert(0); \
                           return(false);}return(false);
#define State(a)           return(true);}} \
                           else if(a == state){if(0){
#define OnEvent(a)         return(true);}else if(a == event){
#define OnEnter            OnEvent(EVENT_Enter)
#define OnUpdate           OnEvent(EVENT_Update)
#define OnExit             OnEvent(EVENT_Exit)
cs

매크로 비법 #9 : 클래스 인터페이스 단순화
1
2
3
4
5
6
7
8
#define INTERFACE_Creature(terminal)               \
  public:                                          \
    virtual std::string GetName() const ##terminal \
    virtual int GetHitPoints() const ##terminal    \
    virtual float GetMaxVelocity() const ##terminal
 
#define BASE_Creature        INTERFACE_Creature(=0;)
#define DERIVED_Creature    INTERFACE_Creature(;)
cs



posted by 알쿠미

GEMS 3권 객체 조합식 프레임워크 분석 (2)

GEMS 2016. 2. 5. 20:21

in Wikipedia

Forward Declaration

Declaration of a variable or function which are not defined yet. Their definition can be seen later on.

Forward Reference

Similar to Forward Declaration but where the variable or function appears first the definition is also in place.


그냥 차이점 정도 알아두자


System_t 클래스 : 게임이 사용할 시스템들에 대한 순수 인터페이스들로의 포인터들을 담는다. 순수 인터페이스들을 통해서 시스템에 접근하므로 동적인 시스템 전환이 가능하며 플랫폼 의존적인 시스템 코드가 시스템들에 접근하는 플랫폼 독립적 코드로부터 분리된다.

시스템 코드에 Systems_t 클래스를 포함시켜도 컴파일러 의존성은 생기지 않는다. 포인터를 통한 시스템으로의 접근에 필요한 것은 전방 참조(Forward reference)뿐이기 때문이다.

물리적 의존성을 줄이는 것은 좋은 물리적 설계를 위한 주요 목표 중 하나이다.


*이러한 시스템들은 모두 순수 인터페이스로 정의되며 구체적인 구현은 모두 은폐되어 있으므로, 시스템 구현들이 정적인 링크 의존성을 가지지 않는 한 이들을 동적으로 전환하는 것이 가능하다.  의존적인 구현들을 동적으로 로드 가능한 구성요소들로 분할하는 것은 패키지 패턴의 한 예이다.


TaskSys_t 클래스는 작업 시스템에 대한 인터페이스를 제공한다.

작업 시스템에 작업을 추가할 때에는 Post_TaskCommand 함수를 이용한다.

작업의 종류는 프레임에 동기화 되는 작업 아니면 비동기 작업이다.

작업이 가지고 있는 Systems_t 포인터는 작업이 자신의 Connect (Sysyems_t *pSysyems) 함수를 통해서 시스템에 연결될 때 전달 받은 것이다.


..일단 내가 생각했던 프레임워크가 아닌거 같으므로 분석은 나중에 해야겠다;

posted by 알쿠미

GEMS 3권 객체 조합식 게임 프레임 워크 (1)

GEMS 2016. 2. 5. 19:42

게임 프레임웍의 설계

플랫폼 독립적 VS 플랫폼 의존적

프레임워크는 운영체제와 플랫폼 기술에 국한될 필요가 없다.

게임의 개념적인 작업을 플랫폼에 국한된 사항들로부터 독립시킬수록, 이식 과정에서 플랫폼에 국한된 코드를 다른 것으로 대체하기 쉬워진다. 따라서 게임 개념들을 구체적인 운영체제와 플랫폼 기술로부터 최대한 또는 가능한 한도 내에서 분리시키기 쉽도록 만드는 것은 게임 프레임 워크 제작의 한 가지 목표가 된다. 또한 플랫폼에 국한된 기능의 구현을 단순화하는 것 역시 중요한 목표라고 할 수 있다.


게임 독립적 VS 게임 의존적

하나의 프레임워크로 하나의 게임을 만든다면 속도향상에는 도움이 될 수 있다.

하지만 하나의 프레임워크로 여러 개의 게임을 만든다면, 그 프레임워크는 게임에 독립적이 되어야 한다.


객체 조합 VS 상속

직관적인 응용 프로그램 프레임 워크를 만드는 한 가지 방법은, 템플릿 메서드 설계 패턴을 적용해서 응용 프로그램 클래스의 서브클래스들을 작성하는 것이다.

* 템플릿 메서드 패턴 : 상위 클래스에서 처리흐름을 제어하며 하위 클래스에서 내용을 구체와 한다.

* 구현보다는 인터페이스를 프로그래밍하라.

* 클래스 상속보다는 객체 조합을 선호하라.


프레임 기반 작동 VS 함수 기반 작동

프레임 타이밍과 관련이 없는 소프트웨어는 함수기반 작동이 편하며 프레임 기반 작동에 피해 프로그래밍하기 훨씬 쉽다.

프레임 기반 작동을 하기 위해서는 프레임에 동기화 되는 작업들을 언제 수행할 것인지 작업 시스템 클래스에게 알려주는 프레임 시스템 클래스가 필요하다.

또한 프레임에 동기화되지 않는 작업들도 처리할 수 있어야한다.

프레임을 몇단계 진행시키거나 특정 프레임률을 선택할 수 있는 능력도 필요하다.


동적인 연산 순서 VS 정적인 연산 순서

게임에선 유저의 행동에 따라 연산순서가 결정되므로 동적인 연산순서가 적당하다.


동적인 객체 수명 VS 정적인 객체 수명( 그리고 소유권 문제들 )

???


수평적 작업 통합 VS 수직적 작업 통합

(객체 조합 시스템)수평적 작업은 프로그래밍 환경이 평면적이고 객체들과의 관계가 동적이다.

작업들은 수평적으로 조직화된다는 느낌을 주며, 시스템의 특정 작업들에 대한 변경은 다른 작업들에게 거의 영향을 미치지 않는다.( 수직적 [계통적 시스템] 과는 다르다)







posted by 알쿠미

GEMS 3권 게임 이벤트 일정관리(스케줄러)

GEMS 2016. 2. 4. 19:48

스케줄러의 개념들

기본적으로 스케줄러는 하나의 작업관리자, 하나의 이벤트 관리자, 하자의 클럭으로 구성된다.

이러한 구성들을 통해서 시간 기반 또는 프레임 기반으로 이벤트들을 발생시키고 이벤트 처리기들을 실행시킨다.


1. 작업 관리자

작업들의 등록 및 조직화를 처리한다. 각 작업은 관리자가 호추할 콜백 함수를 포함하는 표준적인 인터페이스를 가진다.

작업 관리자는 작업들의 목록을 유지하며, 일정 정보(시작 시간, 수행 빈도, 기간, 우선 순위, 그 외에 필요한 속성들)도 관리한다.

또한 사용자 데이터 포인터나 성능 통게 자료도 담는다.


2. 이벤트 관리자

스케줄러의 심장부로서, 작업 관리자 안의 각 작업에는 그 작업에 필요한 하나 혹은 그 이상의 이벤트 들이 정의된다. 이벤트는 작업이 실행될 시간 상의 한 순간에 해당한다.


3. 실제 시간 대 가상 시간

실시간 스케줄러의 개념은 상당히 간단하다. 루프안에서 이벤트 관리자가 계속 실행되면서 실시간 클럭을 주시하다가 대상 시간에 도달하면 이벤트를 발생시키는 것뿐이다.


스케줄러는 시간을 조작함으로써 주어진 하나의 시간 또는 시간의 경과를 실제 시간과는 독립적으로 흉내낼 수 있다.

(가상 시간을 이용한 스케줄러는 작업들을 임의의 시간 단위 또는 속도로 수행할 수 있다는 장점을 가지고 있다.)

* 가상시간을 이용한 스케줄러는 작업들을 임의의 시간 단위 또는 속도로 수행할 수 있다는 장점이 있다.


가상시간 스케줄러는 시간을 프레임들로 분할한다.

작업들은 프레임들 사이에서 가상시간을 기준으로 일괄 수행되며, 프레임이 렌더링될 때 다시 실제 시간과 동기화된다.

 

'GEMS' 카테고리의 다른 글

GEMS 3권 객체 조합식 프레임워크 분석 (2)  (0) 2016.02.05
GEMS 3권 객체 조합식 게임 프레임 워크 (1)  (0) 2016.02.05
GEMS 2권 스택와인딩(stack winding)  (0) 2016.02.04
GEMS 2권(6)  (0) 2016.02.04
GEMS 2권 (5)  (0) 2016.02.03
posted by 알쿠미

GEMS 2권 스택와인딩(stack winding)

GEMS 2016. 2. 4. 17:49

스택 와인딩(stack winding)?

C/C++ 프로그래밍 기법으로는 불가능하거나 구현하기 힘든 어떠한 일을 프로그램의 스택을 조작하는 방식으로(어셈블리 코드를 이용해서) 해결하는 매우 강력한 기법이다.


임시적 반환(Temporary Return)?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.586
.model flat
.data
buffer dd ?
file_handle dd ?
filesize dd ?
.code
 
_TempRetEg:
 
    call fn0
    call fn1
    ;
    ;    이전
    ;
    pop edx
    call edx
    ;
    ;
    ;
    call fn2
    call fn3
    ret
 
    A: call _TempRetEg
    ret
 
end
 
cs
MyFunc라는 함수가 _TempRetEg를 호출한다고 한다면 _TempRetEg는 fn0와 fn1을 차례대로 호출하고

pop edx, call dex 를 만나게 된다.


25행의 CALL 명령을 CPU가 처리하는 방식은

- 명령이 있는 행의 다음 행(26) 주소가 스택에 쌓이며, 그 후 8행으로 JUMP가 일어난다.

- 16행에 의해 스택에 있는 주소가 뽑아져 나와서 edx라는 CPU 레지스터에 저장된다.

(17줄은 그 주소를 호출하는 것이다.)

위에서 말하는 주소는 MyFunc가 이 루틴을 호출했던 곳 바로 다음 지점이다.


MyFunc가 반환되면(return 문이나 함수의 끝에 도달해서), 비슷한 과정을 통해 다시 _TempRetEg의 21 행으로 돌아간다.

그 후 fn2와 fn3이 호출된 뒤 26행의 ret에 의해 다시 어떤 주소로 돌아간다.

이 어떤 주소는 MyFunc함수를 호출했던 곳 바로 다음 주소이다.

이렇게 스택의 값들을 조작하면 함수 호출과 반환 방식을 임의로 바꿀 수 있다.

ex) fn0가 파일을 열고, fn1이 버퍼를 할당하고 메모리에 읽어들이고, fn2가 그 메모리를 해제하고, fn3이 파일을 닫는다고 하면, MyFunc는 메모리나 파일에 대한 마무리 작업에 대해 신경을 쓰지 않아도 된다.

즉, 파일을 열고 메모리 읽고, 메모리 해제하고 파일을 닫는과정이 모두 하나의 단일한 코드 블럭안에 들어가게 되는것이다.

MyFunc는 그냥 _Temp_RetEg만 호출하고, 버퍼를 사용하고 반환하면 된다.


일단 여기까지 간단한게 보고 다음에 다시 보도록하자.


'GEMS' 카테고리의 다른 글

GEMS 3권 객체 조합식 게임 프레임 워크 (1)  (0) 2016.02.05
GEMS 3권 게임 이벤트 일정관리(스케줄러)  (0) 2016.02.04
GEMS 2권(6)  (0) 2016.02.04
GEMS 2권 (5)  (0) 2016.02.03
GEMS 2권 DLL로 부터 C++클래스 내보내기  (0) 2016.01.22
posted by 알쿠미

GEMS 2권(6)

GEMS 2016. 2. 4. 16:34

1.10 드롭-인 메모리 관리자

메모리 관리자의 기본적인 목표는 메모리 누수를 보고하고, 할당된 메모리 대 실제로 쓰인 메모리의 비율을 계산하고, 경계 위반을 경고하는 것이라 할 수 있다.

메모리 관리자를 사용하기 위해 어떠한 함수를 명시적으로 호출하거나 클래스를 선언해야 할 필요가 없어야 한다. 즉, 헤더파일 한두개를 포함시키는 것 만으로 메모리 관리자가 작동할 수 있도록 해야한다.

메모리 관리자를 사용하면 메모리를 할당, 해제 하는데 필요한 부담을 안게 된다.

그렇기 때문에 게임의 최종 빌드에서는 메모리 관리자를 제거할 필요가 있다.

코드의 수정을 최소화 하기 위해서 디버그 빌드에서만 또는 ACTIVATE_MEMORY_MANAGER같은 매크로가 정의되어 있는 경우에만 메모리 관리자가 작동하도록 만들어야 할 것이다.


1.11 프로파일링 모듈을 만드는 이유

1. 프레임 기반 분석. 게임에서의 단 몇초의 차이도 매우 크다. 그렇기 때문에 여러 병목현상을 분석하기에 프레임 기반 분석이 필수적이다.

2. 언제, 어디서도 분석이 가능하다. 게임 개발 중반, 막바지, 어느 컴퓨터 등 언제 어디서든 분석이 가능하다.

3. 커스텀화. 요즘 게임엔진들은 매우 복잡하다. 수많은 모듈들 중 의심이 가는 특정 모듈에 대해서만 성능 분석을 수행할 수 있다면 매우 편할 것이다.


프로파일링 모듈의 요구사항들

1. 사용자가 응용 프로그램을 빠르게, 그리고 정확하게 프로파일링 할 수 있어야한다.

2. 수행 부담이 적어야 한다.

3. 여러 사용자들이 다른 엔진 모듈들에 대해 신경쓰지 않고 자신의 시스템에서 개별적으로 작업할 수 있어야한다.

4. 프로파일러가 필요없어지면 완전히 제거할 수 있어야 한다.


1.12 윈도우즈 기반게임을 위한 선형적 프로그래밍 모델

다중 스레딩 - 메세지 펌프를 하나의 스레드에 넣고 게임을 다른 스레드에 넣자는 것

MainThread는 RunGame함수를 호출하고 그 함수가 종료되면 WM_CLOSE 메세지를 전송해서 메시지 펌프스레드의 실행을 종료시킨다.

Alt-Tab 문제 - 쓰레드간의 통신을 위한 도구인 이벤트를 사용하도록하자.

(게임의 시작시점에서 수동 재설정 이벤트를 하나 만들고 이 이벤트는 프로그램이 비활성화 되면 false, 활성화 되면 true로 설정되어야 한다.)

게임 루프안에서 매번 호출되는 함수안에

WaitForSingleObject( task_wakeup_event, INFINITE );

와 같은 코드를 넣으면 된다.

그러려면 윈도우 프로시저 루프안에서 APP_ACTIVATE 메시지를 잡아내고 현재 이 응용 프로그램이 활성화되어 있는지 확인해야한다.

만약 비활성화 상태라면 ResetEvent( task_wakeup_event ); 

다시 실행을 재개할 때에는 SetEvent( task_wakeup_event );

* 게임이 비활성화 상태에서도 게임 세계가 갱신되어야 한다면, 게임 스레드 전체가 아니라 렌더링 파이프라인만 유보시키고 게임 세계의 갱신 루틴은 계속 실행되게 만들면 된다.


posted by 알쿠미

GEMS 2권 (5)

GEMS 2016. 2. 3. 15:39

1.6 dti(dynamic type information)

1.7 property/ propertyset


1.8 게임 개체 팩토리

프레임워크 설계에서 상정한 목표들

1. 논리적 행동과 시청각 행동의 분리

2. 빠른 개발.

3. 코드 중복의 방지


구성요소 - 플라이급, 행동, 익스포트된 클래스

- 플라이급 : 개체의 모양과 느낌, 객체 조립 패턴을 통해서 구현된다.

 * 객체는 하나의 플라이급 객체에 대한 포인터를 가지며, 그 것을 통해서 자신의 시청각적 특징을 표현한다.

- 행동 : 객체가 게임세계와 상호작용하는 방식, 전통적인 상속방식으로 구현된다.

 * Entity라는 클래스가 그 계통구조의 추상기반 클래스이다.

- 익스포트된 클래스 : 객체가 자신을 세계에 알리는 방식을 정의한다. 하나의 열거형 상수로 구현되며(편의를 위해서), 하나의 개체가 자신의 수명동안 몇 가지 서로 다른 객체들을 통해서 자신을 알릴 수 있도록 한다.


- 플라이급

다양한 상황들에서 동시적으로 공유, 사용될 수 있도록 자신의 문맥(context)을 박탈당한 객체라고 설명하고 있다.

개체의 현재 상태를 제외한 모든 것들이 플라이급 정보라고 할 수 있다.

* SAMMy : 상태 및 매체 관리자


- 행동

템플릿 메서드 패턴

기본 클래스에 처리하는 기본 함수를 만들어두고 그 안에 가상함수를 넣어처리하는 방법인듯하다.


- 익스포트된 클래스

스크립트 작성자가 게임 개체의 내부 상태 정보를 알 필요가 없도록 만드는 아주 편리한 기법


전략 패턴 - 컴포넌트 시스템의 기본이 되는 패턴.


클래스 안에서 함수포인터를 사용하기 위해선 스크립트 컴파일러에 의해 생성된 함수를 클래스의 friend함수로 선언하는 것. friend함수로 선언하는 이유는, 그 함수가 클래스의 private 데이터 멤버들에 접근할 수 있도록, 그리고 그 함수를 현재 객체를 뜻하는 특별한 포인터인 this에 첫번째 인수로서 넘겨 줄 수 있도록 하기 위해서이다.


class SomeEntity : public Entity

{

//함수 포인터

void ( * friendptr ) ( Entity * me, Entity * target );

public : 

//하나나 그 이상의 전략 함수들을 friend로 선언한다.

friend void Strategy1( Entity * me, Entity * target);

...

//실제 작동

void HandleInteractions ( Entity * target )

{

(* friendptr) (this, target) ; // this를 첫번째 인수로서 넘겨주기 위한것

}

}








'GEMS' 카테고리의 다른 글

GEMS 2권 스택와인딩(stack winding)  (0) 2016.02.04
GEMS 2권(6)  (0) 2016.02.04
GEMS 2권 DLL로 부터 C++클래스 내보내기  (0) 2016.01.22
GEMS 2권 추상 인터페이스  (0) 2016.01.22
GEMS 2권 인라인 VS 매크로  (0) 2016.01.21
posted by 알쿠미

GEMS 2권 DLL로 부터 C++클래스 내보내기

GEMS 2016. 1. 22. 17:27


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifdef _BUILDING_MY_DLL
#define DLLFUNCTION __declspec(dllexport// DLL을 빌드하는 경우 
이것이 정의된다.
#else
#define DLLFUNCTION __declspec(dllimport// 응용 프로그램을 빌드하는 경우 
이것이 정의된다.
#endif
 
class DLLFUNCTION CMyExportedClass
{
    public:
        CMyExportedClass(void) : mdwValue(0) {}
        void setValue(long dwValue) { mdwValue = dwValue; }
        long getValue(void) { return mdwValue; }
        long clearValue(void);
    private:
        long mdwValue;
};
cs


하지만 이 인스턴스롤 소멸시킬때 문제가 되는데 그런 문제를 방지하기 위해서 DLL에서 도우미 함수를 호출해서 소멸시키도록 하는 것이 좋다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifdef _BUILDING_MY_DLL
#define DLLFUNCTION __declspec(dllexport// DLL을 빌드하는 경우
 이것이 정의된다.
#else
#define DLLFUNCTION __declspec(dllimport// 응용 프로그램을 빌드하는 경우
 이것이 정의된다.
#endif
 
class  CMyExportedClass
{
    public:
        CMyExportedClass(void) : mdwValue(0) {}
        DLLFUNCTION void setValue(long dwValue) { mdwValue = dwValue; }
        DLLFUNCTION long getValue(void) { return mdwValue; }
        long clearValue(void);
    private:
        long mdwValue;
};
 
CMyExportedClass *createMyExportedClass(void) {
    return new CMyExportedClass;
}
void deleteMyExportedClass ( CMyExportedClass *pclass) {
    delete pclass;
}
cs

사용자가 도우미 함수를 무시하고 객체를 생성하고 소멸하는것을 막기위해서 클래스 자체를 내보내는 대신 생성과 소멸에 관한 함수들을 내보내지 않는것이다.


가상 클래스 멤버 함수 내보내기

클래스로 부터 멤버함수를 내보낼 때 unresolved external symbol 이 나타날 때 가 있다.

실행시점에서 LoadLibrary 함수르 DLL을 로드하는 경우, 인라인 함수를 비활성화 시킨상태에서 빌드 할 경우

인라인 함수 확장 옵션을 "Only __inline"이나 "Any Suitable"로 설정하는 것도 있지만

멤버함수를 virtual로 선언해서 내보내도 해결할 수 있다. 인라인 확장에 관련된 문제도 피할 수 있다.


DLL으로 부터 클래스를 내보내는 것은 소스코드를 공개하지 않고도 기능성을 공유하기 위한 쉽고도 강력한 방법이다.




'GEMS' 카테고리의 다른 글

GEMS 2권(6)  (0) 2016.02.04
GEMS 2권 (5)  (0) 2016.02.03
GEMS 2권 추상 인터페이스  (0) 2016.01.22
GEMS 2권 인라인 VS 매크로  (0) 2016.01.21
GEMS 2권 (1)  (0) 2016.01.21
posted by 알쿠미

GEMS 2권 추상 인터페이스

GEMS 2016. 1. 22. 16:10

추상 인터페이스는 어떠한 구현도 제공하지 않는다. 규칙만 정의한다.

이러한 설계의 장점은

- 상속받는 새 클래스를 만들고 인스턴스화 하기만 하면 나머지 부분은 수정하지 않아도 된다.

- 실행시점에서 두 클래스들을 전환할 수 있다.

- 인터페이스를 제외한 세부사항들에 대해서 사용자에게 알려주지 않아도 된다.(은폐되어 있다.)

- 코드가 깔끔하다.


* 특정 클래스로 형변환해서 클래스에 특정 기능에 접근하거나 특정 구현에 국한된 작동 방식에 의존하려는 행동은 추상 인터페이스로 인한 이득이 사라질 수 있으니 방지하는 것이 좋다.


추상 팩토리란?

하나의 인터페이스에 대한 특정 구현을 요청에 따라 인스턴스화 하는 것이 유일한 목적인 클래스


* 추상 인터페이스는 항상 가상 소멸자를 가져야 한다.

 그렇지 않으면 C++은 자동적으로 비가상 소멸자를 생성하며, 그러면 구체적인 구현의 실제 소멸자가 호출되지 않는다.

일반적인 멤버 함수들과는 달리, 소멸자는 '순수' 가상 소멸자로 지어할 수 없으므로, 컴파일러를 만족시키기 위해서는 가상 소멸자를 빈 함수로 정의해 주어야한다.


다중상속 시에도 편리하다.

다중상속을 피해야 한다고 말하지만 추상클래스의 경우 커다란 단점은 없다.

마름모꼴 상속구조가 될 가능성이 없기 때문이다.


추상 클래스의 단점

설계가 복잡해진다. 이득이 확실한 경우에만 사용하며, 무작정 적용하면 안된다.


디버깅이 어려워 질 수 있다. 인터페이스를 제대로 사용하지 않았을 때에만 버그들이 생길 수 있도록 견고하게 만들어야 한다.


상속을 통해서 추상 인터페이스 확장이 불가능 하다.

파생 클래스를 이용해서 새로운 인터페이스를 제공하는 것은 여전히 가능하나, 그런 것들은 모두 게임 코드가 아니라 클래스 구현 측면에서 이루어져야 할 일이다.


추상 인터페이스 안에 함수는 모두 가상 함수이다.

가상 함수는 호출하기 위해서 거쳐야할 단계가 하나 더 있기 때문에 빈번히 호출되는 함수들을 추상 인터페이스에 적용하는 것은 좋지 않다.








'GEMS' 카테고리의 다른 글

GEMS 2권 (5)  (0) 2016.02.03
GEMS 2권 DLL로 부터 C++클래스 내보내기  (0) 2016.01.22
GEMS 2권 인라인 VS 매크로  (0) 2016.01.21
GEMS 2권 (1)  (0) 2016.01.21
GEMS 1권(4)  (0) 2016.01.20
posted by 알쿠미

GEMS 2권 인라인 VS 매크로

GEMS 2016. 1. 21. 21:52

인라인 함수의 장점

인라인 함수의 모든 매개변수들은 한 번만 평가된다.

#define square(x) (x*x)

이 매크로에 2+1을 넣으면 5가 된다. 인라인에서는 9가 된다.


인라인 함수는 일반 함수에 적용되는 모든 형 안전성 프로토콜들을 따른다.


inline이라는 키워드를 추가한다는 점을 제외하면 일반함수와 동일한 구문을 통해 정의된다.


인라인 함수에 인수로서 전달된 표현식들은 함수 본문에 진입하기 전에 평가된다.

매크로에 넘겨지는 표현식들은 두 번 이상 평가될 수 있으며, 그러면 안전하지 못한, 그리고 예기치 못한 부작용이 생길 수 있다.


인라인 함수는 디버깅이 가능하지만 매크로는 파서가 코드를 해석하고 프로그램의 심볼 테이블을 만들기 전에 확장되므로, 매크로 자체를 디버깅하는 것은 불가능하다.


인라인 함수는 일반 함수 호출과 동일한 구문을 따르며 매개변수들이 예기치 않은 방식으로 수정되는 일이 없으므로 코드의 가독성과 관리 용이성을 증가시킨다.


매크로는 형이 자유롭지만 인라인 함수는 매개변수 형에 제한을 둔다. 하지만 템플릿을 이용하면 형에 자유로워질 수 있다.


인라인함수는 언제 사용하는가?

인라인 함수는 호출되는 곳마다 코드가 삽입되므로 프로그램의 크기가 매우 커진다.

코드 크기가 커지면 프로그램에 대한 메모리 요구량이 많아져서 캐시 적중 실패나 페이지 실패가 일어날 가능성이 높아진다.


- 작은 메서드들 - private멤버에 접근하는 메서드들

- 객체에 대한 상태정보를 돌려주는 함수들

- 작은 함수들 3~4줄짜리

- 자주호출되는 작은 함수들. 속도가 중요한 렌더링 루프안에서 호출되는 함수들


매크로는 언제 사용하는가?

#은 매크로 매개변수를 문자열 상수로 변환하는 문자열화 연산자이다.

작은 의사코드를 만들 때 매우 유용하다.


마이크로소프트에 국한된 사항

__inline 키워드는 컴파일러가 비용/이익을 분석을 수행하고 이익이 있을경우 함수를 인라인화 한다.

__forceinline 키워드는 함수를 항상 인라인으로 만들도록 강제한다.

위에 키워드를 쓰더라도 인라인을 적용하지 않을 때가 있다.








'GEMS' 카테고리의 다른 글

GEMS 2권 DLL로 부터 C++클래스 내보내기  (0) 2016.01.22
GEMS 2권 추상 인터페이스  (0) 2016.01.22
GEMS 2권 (1)  (0) 2016.01.21
GEMS 1권(4)  (0) 2016.01.20
GEMS 1권 (3)  (0) 2016.01.15
posted by 알쿠미

GEMS 2권 (1)

GEMS 2016. 1. 21. 19:55

프로파일링을 신경쓰자 (직접 수치를 봐야 알 수 있는 것들이 있기 때문이다.)


객체의 생성과 소멸

- 객체가 필요하게 되기 전까지는 생성하지 말 것

- 루프에선 생성하지 말 것


초기화 목록 사용

- operator=을 거쳐서 초기화 되는 것은 비효율 적이기 때문에


후증가 보단 선증가 사용

- 후증가는 복사본을 만들고 반환해야한다.


값을 반환하는 연산자는 피하자

operator+ 같은 경우에는 새로운 객체를 반환해야 하기 때문에

임시객체를 반환하지 않는 += 같은 단항 연산자를 사용하는 것이 좋다.


가벼운 생성자를 사용하자

operator= 보다는 복사생성자가 좋다.

매개변수 하나짜리 생성자를 explicit으로 선언하는 습관을 들이는 것이 좋다.

(형을 변환할 때 컴파일러가 내부적인 임시 객체를 생성하는 일을 방지할 수 있다.)


객체를 미리 할당하고 캐싱할 것

빈번하게 할당, 해제되는 클래스들을 사용할 때는 일일이 할당하기 보단 미리 할당하는게 좋다.


메모리 관리

new와 malloc()을 재정의 하여 사용하는 것이 좋다.(단편화 방지)


가상함수

가상 함수 호출의 추가적인 부담은 가상함수 테이블에 대한 간접적인 접근에서 기인한다.

호출의 주소를 미리 알 수 없기 때문에 명령어 캐시 적중률이 낮아진다.

작고 자주 쓰이는 객체들은 계통적인 클래스 상속 구조에서 독립시키는 것이 좋다.


코드 크기


STL

STL에 관한 여러 예시와 조심할 점 (59p)






'GEMS' 카테고리의 다른 글

GEMS 2권 추상 인터페이스  (0) 2016.01.22
GEMS 2권 인라인 VS 매크로  (0) 2016.01.21
GEMS 1권(4)  (0) 2016.01.20
GEMS 1권 (3)  (0) 2016.01.15
GEMS 1권(2) STL  (0) 2016.01.15
posted by 알쿠미

GEMS 1권(4)

GEMS 2016. 1. 20. 19:20

프레임 기반 메모리할당

충분한 메모리 블록을 할당하고 위에 프레임 아래 프레임 해서 위에서는 내려오고 아래서는 올라오는 메모리 구조이다.


#define ALIGNUP( nAddr‘ ess, nBytes ) ( (((uint)nAddress) + \ (nBytes)-1) & (~((nBytes)-1)) ) 

이게 2의 제곱일때 제대로 작동한다.


먼저 생성된 메모리가 먼저 해제되어야 하는 메모리 할당 구조이다.

위에 프레임 아래 프레임 나눠서 다른 종류의 데이터를 할당하는 것이 좋다고 한다.


1.10 BitArray / 프록시 클래스?


1.11 체크섬? / 해킹을 막기위한 프로토콜을 얘기하는 것 같다.


1.2 assert

assert는 릴리즈에서는 호출되지 않으므로 assert안에서는 어떠한 값의 변경도 있어서는 안된다.

 1. 더 많은 정보를 집어 넣는다.

   assert( src != 0 && "VectorNormalize: src vectorpointer is NULL" );

   이렇게 하면 src != 0 && "VectorNormalize: src vectorpointer is NULL" 경고창에 이 문자가 뜨기 때문에 에러를 판별하기 쉽다.


  2. 좀더 많은 정보를 집어 넣는다.

    도달하지 말아야 할 곳에 assert(!"The code should never get here");

    이렇게 넣으면 된다.


  3. 좀더 편하게

    그냥 assert( 0, ""); 아래 , 이것으로 구분해두면 ""안에 있는 에러메세지만 표시된다.


  4. 커스텀 assert 만들기

    디버거 하에서 조건이 실패할 경우 소스파일안에 assert문이 아니라 assert.cnot 파일 안에서의 assert문으로 들어가게 된다.

    소스 파일의 assert문으로 들어가게 하기위한 custum assert구문을 제시한다.


  5. 특정 assert를 한번만 아니면 아예 실행안되게 할수도 있다.


  6. assert 대화상자에 스택정보를 표시할 수 있다.[Robbins99 참조]


  7. assert 대화상자에 클립보드에 스택내용을 복사하는 버튼을 만들자.

    일일이 내용을 적을 필요 없이 복사를 하자.


1.13 게임을 디버깅 할 때 요소들을 간단하게 표현하고 콘솔게임을 만들 때 어려운 디버깅을 쉽게 할 수 있는 기능.


1.14 프로파일링 - 게임을 디버깅함에 있어서 병목현상 등 여러 문제점을 수치로 표현하여 보기 쉽게 한 기능?


3.0 FSM 나중에 구현할 때 참조하자












'GEMS' 카테고리의 다른 글

GEMS 2권 인라인 VS 매크로  (0) 2016.01.21
GEMS 2권 (1)  (0) 2016.01.21
GEMS 1권 (3)  (0) 2016.01.15
GEMS 1권(2) STL  (0) 2016.01.15
Gems 1권 (1)  (0) 2016.01.14
posted by 알쿠미

GEMS 1권 (3)

GEMS 2016. 1. 15. 17:02

RPC( remote procedure call) 원격 프로시저 호출


__cdecl : 호출자가 스택을 비운다. 즉 호출이 완료된 후 스택으로부터 인자들을 뽑아내는 작업을 호출자가 책임진다. 호출된 함수가 인자들의 정확한 개수를 알아야할 필요가 없다.(가변인자 함수에 필수적)

C, C++ 의 전역 함수와 정적 함수가 기본적으로 사용하는 것이 이 호출규약이다.


__stdcall : 호출된 함수가 스택을 비운다. Win32 API 호출에 쓰이는 표준 규약이다.

(클라이언트 코드 크기의 관점에서 좀더 효율적이라는 이유 때문인 것 같다.)


FPU : 부동소수점 연산을 효율적으로 사용하기위한 하드웨어 논리회로 모듈


eax, edx 레지스터도 있음


RPC를 하는 함수? 를 만드는 방법을 제시한다. (1.5장)


범용 핸들기반 자원 관리자 (1.6장)

핸들을 이용하면 메모리 주소가 한다리 건너 있는 것이기 때문에 내부적인 데이터가 변한다 해도 핸들이 무효화되는 일은 없다.( 포인터를 사용하는 것보다 안전하다)


두개의 비트필드로 이루어진 unsigned int를 사용하여 첫번째 요소는 핸들 관리자 데이터베이스로의 빠른 역참조를 위한 고유한 식별자이고(std::vector를 이용하는 것이 효율적), 두번쨰 요소는 핸들의 유효성을 검사하는데 쓰일수 있는 하나의 매직넘버. 역참조시에 핸들 관리자는 핸들의 매직넘버가 그에 해당하는 데이터베이스 항목과 일치하는지를 점검한다.


비트필드?

비트별로 데이터를 다룰 수 있도록 해주는 구조체의 문법에서 특별 용법이다.

비트 필드는 자료형을 쪼개서 비트단위로 사용할수 있게 하는 방법이다.






'GEMS' 카테고리의 다른 글

GEMS 2권 (1)  (0) 2016.01.21
GEMS 1권(4)  (0) 2016.01.20
GEMS 1권(2) STL  (0) 2016.01.15
Gems 1권 (1)  (0) 2016.01.14
GEMS 1권 1.0 데이터 주도적 설계  (0) 2016.01.12
posted by 알쿠미

GEMS 1권(2) STL

GEMS 2016. 1. 15. 15:47

STL


stl은 begin()과 end()가 있는데 begin()은 첫번쨰 요소를 가리키고 end()는 마지막 요소 다음 요소로 역참조해선 안되는 주소이다.


STL컨테이너에는 객체 자체를 넣기 보다는 객체의 포인터를 넣어주는 것이 좋다.

값을 얻을 떄 객체를 복사하니까?


벡터

벡터의 끝에 하나의 요소를 추가하는 것은 상각된(amortized) 상수적 시간이다.

어떤경우에는 새 메모리를 할당하고, 기존 메모리에 있는 배열을 새메모리로 복사하고, 기존 메모리를 해제하는데 추가적인 시간과 자원이 소모된다.

reserve()로 미리 할당하고 그만큼 꽉찼을 때 요소를 삽입하게되면 메모리 재할당이 일어나며 현재의 모든 반복자들이 무효화 된다.

push_front()나 push_pop()은 사용하지 않는 것이 좋다. 객체들을 뒤로 미는데 걸리는 시간은 O(n)이기 때문이다.

[]로는 벡터의 요소값을 참조하거나 기존 요소값을 변경하는 것만 가능할 뿐 새 요소를 삽입하는 것은 불가능 하다.


List

어떠한 요소 삽입이나 삭제도 상수적 시간으로 수행된다. 그렇기 때문에 임의 접근은 할 수 없다.

(요소들을 순차적으로 접근해서 원하는 요소에 접근해야한다.)


리스트를 지울때는 먼가 delete (*iter)후에 erase(iter)를 해주어야 한다.(할당된 메모리를 명시적으로 해제해 주어야 한다.)

Sort함수는 포인터의 경우 주소값을 비교해서 정렬하기 때문에 비교 연산자를 오버로딩해야한다. 컨테이너를 복사하는 것은 객체 자체가 아니라 포인터들이다.

erase()함수는 반복자가 유효한 위치를 가리키도록 한다. 그러므로 erase()함수의 반환값을 반복자에 넣으면 반복자를 2번 증가시키게 된다. 그러므로 erase()하지 않았을때 증가시켜야한다.


데크(deque)

컨테이너의 양끝에서 요소의 삽입과 제거가 일어나야하며, 중간에서는 요소의 삽입이나 제거가 일어날 필요가 없을 때 쓰인다.

벡터와 마찬가지로 컨테이너의 앞과 뒤에서 상각된 상수적 시간으로 삽입가 제거를 수행한다.

큐 자체 내부 데이터의 복잡한 본성 때문에 벡터에서보다 임의 접근이 더 비효율적이다.


벡터와는 달리, 데크에는 추가적인 메모리 할당이 정확히 언제 일어나야 할 것인지 결정하는 메커니즘이 없다.

(reverse_iterator 얘기가 있었음)


키 값을 통해서 값을 조회하는데 걸리는 시간은 O(log n)인데, 해시 테이블 보다는 비효율적이지만 속도의 차이는 무시할 수 있을 정도이며, 삽입과 함께 정렬이 수행된다는 추가적인 장점이 존재한다.(map의 데이터는 항상 정렬된 상태로 존재한다)

저장방식(균형 이진트리, 적흑 이진 트리)이 가지고 있는 장점을 가지고 있다.

find()함수의 수행시간은 O(log n)이다.

키에 기반해서 찾는 것은 O(log n)이지만, 값(second)에 기반해서 찾는 것은 O(n)이 된다.

map에서는 erase()함수가 다음의 유효한 위치를 돌려주도록 구현하지 않았다.

그래서 다른 컨테이너들과는 조금 다른 방식으로 요소를 제거해야한다.


스택, 큐, 우선순위 큐 <- 기존 컨테이너들 위에 놓인 제한적인 인터페이스들


스택

기본적으로 하나의 데크로 구현되다.

pop()이나 top()를 수행할 때 실제로 스택 요소가 존재하는지 검사하지 않는다.

그렇기 때문에 함수를 호출하기 전에 size()나 empty()등을 이용해서 스택이 비어있는지 확인해 주어야한다. (큐나 우선순위 큐도 마찬가지)

직접 내부 자료구조를 지정하는 것이 가능하다.

stack<int, vector<int> > c; // 이런식으로


back()은 요소가 삽입될 위치, front는 요소가 제거될 위치

스택에서처럼 내부 자료구조를 지정하는 것이 가능하나 vector를 사용하는 것은 좋지 않다.


우선순위 큐

요소가 삽입되는 즉시 <(미만) 연산자를 통한 비교에 기반해서 내림차순으로 정렬된다.

객체에 대한 포인터를 다룰때에는 사용자 정의함수로 <연산자를 대체하여 사용해야 한다.

(그렇지 않으면 값의 순서가 아니라 주소순서로 정렬하기 때문에)
















'GEMS' 카테고리의 다른 글

GEMS 2권 (1)  (0) 2016.01.21
GEMS 1권(4)  (0) 2016.01.20
GEMS 1권 (3)  (0) 2016.01.15
Gems 1권 (1)  (0) 2016.01.14
GEMS 1권 1.0 데이터 주도적 설계  (0) 2016.01.12
posted by 알쿠미

Gems 1권 (1)

GEMS 2016. 1. 14. 18:11

생성자는 리턴값이 없으므로 안에 Create()같은 함수를 이용해서 값을 반환하도록 한다.

Bool값을 반환해서 할 수도 있지만 예외처리 매커니즘으로 할 수도 있다.

하지만 예외처리는 속도상 부담이 될 수 있다.


소멸자는 Destroy()를 호출하여 자원 패제나 객체 소멸, 재생성 할 수 있게 한다.

하지만 Create()와 쌍을 이루지 않고 호출되어도 문제가 생기지 않도록하며 마지막에 Clear()함수를 호출하게 한다.


싱글턴을 사용하는이유

모든 소스파일에서 extern문으로 전역객체를 선언하는 것 보다는 하나의 함수를 통해서 객체에 접근하는 것이 훨씬 편하고 각체의 생성 초기화 시점을 더 정밀하게 제어할 수 있다.

객체 포인터를 사용한다면 객체에 대한 모든 접근들을 제어할 수 있게 된다.


퍼사드 패턴

'관리자는 클래스들 사이의 상호희존성을 최소화 시키는데 필주적이다. (매니저 클래스 개념)

구현 원칙은 "가능한한 서브시스템 내부의 클래스들을 시스템 외부로 노출시키지 말라"


스테이트 패턴(상태 패턴) FSM을 생각하면 되겠다.


팩토리 패턴


템플릿 인자에서 부동수수점 수에 대한 지원 문제.

C++표준에는 "형이 없는 종류의 템플릿 인자는 부동소수점...형을 가지도록 선언될 수 없다."

이에 대한 해결책은

template< double& R> struct Sine

처럼 참조 인자를 사용하는 것이다.



템플릿을 메타프로그래밍을 한다고 해서 무조건 빠른것은 아니다.

효과가 검증되기 전까지는 의심해봐야 한다.


책에 있는 코드중

int offset = (int)T*1 - (int)(Singleton)<T>*)(T*)1;

이 부분은 주소값의 거리를 계산해서 지금 만들어지는 싱글톤이 단일 객체인지 확인하는 과정

이다.


(템플릿 메타 프로그래밍 코드는 나중에 분석하도록 하자)











'GEMS' 카테고리의 다른 글

GEMS 2권 (1)  (0) 2016.01.21
GEMS 1권(4)  (0) 2016.01.20
GEMS 1권 (3)  (0) 2016.01.15
GEMS 1권(2) STL  (0) 2016.01.15
GEMS 1권 1.0 데이터 주도적 설계  (0) 2016.01.12
posted by 알쿠미

GEMS 1권 1.0 데이터 주도적 설계

GEMS 2016. 1. 12. 17:22

- 텍스트 파일을 읽어들일수 있도록 하자


- 상수들을 하드 코딩해서는 안된다.


* 그렇게 해야 코드를 컴파일 하지 않고 상수를 수정할 수 있다.

ex)카메라 이동 같은 기본적인 기능들은 텍스트 파일들을 통해서 외부로 노출시켜야 한다.

(프로그래머 이외의 사람들이 간단하게 편집할 수 있도록 함이다.)


- 하드 코딩을 하지 말자

* 설계상의 결정 사항들을 자유롭게 수정할수 있도록 하드코딩을 해선 안된다.


- 게임의 흐름제어는 스크립트로 하자

* 스크립트란 프로그램 코드 밖에서 정의하기 위한 하나의 수단이다.

* 단계들을 정의하거나 이벤트를 발생하게 하는 데 주로 사용된다.

* 조건 분기 방식에 신경을 써야한다.

 1. 변수들을 유지시키고 = 이나 < 같은 연산자를 이용해서 비교하는것

 2. IsLifeBelowPercentage(50) 같은 함수로 변수를 비교하는 것

이 두가지 방법이 있다. 함수쪽이 디버깅이 편하다

스크립트를 사용하려면 스크립팅 언어가 필요하다. 완전 새로운 문법을 직접 정의해야하며 스크립트 파서도 직접 만들어야한다.


- 그렇다고 스크립트를 남용해선 안된다.

 * 복잡한 로직은 코드에 데이터는 코드외부에 두어야한다.

 * 스크립트가 위험한 이유는 데이터의 성격과 로직의 성격을 함꼐 가지고 있다는 점

 (그렇기 때문에 스크립트에 로직을 넣고 싶어지는데 그렇게 되면 코드를 작성하는 것과 차이가 없어진다.)

 * 복잡한 로직을 코드안에 두어야 프로그램 기능성과 디버깅에 좋다.

 * 코드와 스크립트 사이의 경계가 애매하다.

(로직이 복잡하면 코드에 넣고, 스크립트는 최대한 단순한 상태로 유지해야한다.)


- 데이터 중복을 피하자

 * 여러곳에서 쓰일 데이터를 전역적인 데이터로 만드는 것 또한 특정한 위치에서는 그러한 데이터 일부를 수정하게 할 수도 있다. 이를 위해서는 상속이라는 개념이 도입되어야 한다.


- 데이터를 만들어 내는 도구를 작성하자











'GEMS' 카테고리의 다른 글

GEMS 2권 (1)  (0) 2016.01.21
GEMS 1권(4)  (0) 2016.01.20
GEMS 1권 (3)  (0) 2016.01.15
GEMS 1권(2) STL  (0) 2016.01.15
Gems 1권 (1)  (0) 2016.01.14
posted by 알쿠미