스터디 그룹
스터디 한 것
게임엔진 아키텍처 3판 3장 - “게임을 위한 소프트웨어 엔지니어링 기초” 중
에러 감지와 처리
에러의 종류
- 크게 두가지가 있음
- 사용자 에러 : 사용자가 뭔가 잘못해서 나는 에러
- 프로그래머 에러 : 프로그램의 잘못된 버그
- 사용자 개념은 상황에 따라
- 플레이어
- 게임 제작자(기획, 아트)
- 프로그램(미들웨어)을 사용하는 프로그래머
에러처리
플레이어 에러 처리
- 시각, 청각적 효과로 알림
개발자 에러 처리
- 에셋을 빨리 발견해 수정 or 에러를 대비한 예외처리 코드
- 저자는 두 가지 절충 하란다
프로그래머 에러 처리
- 가장 좋은 방법은 에러 감지 코드를 소스 곳곳에 넣어서 프로그램을 멈추게 하는 것 -> Assertion System
- 모든 에러를 다루는데 Assertion을 사용 할 수는 없음
에러 감지와 에러 처리 구현
에러 리턴 코드
- 널리 쓰이는 기법
- 처음 에러를 감지한 함수에서 특정한 에러 코드를 리턴하게 하는 방법
- TRUE/FALSE or Sentinel Value
예외 처리
- 리턴 코드의 단점 - 해당 에러 코드를 에러 감지한 함수에서 처리 할 수 없는 경우에는?
- 에러 코드 처리 위치가 main 함수에 위치 해 있고 에러 발생한 함수의 콜 깊이가 40개 넘는다면..
- Throw exception - 어떤 함수가 처리할지 전혀 신경 안쓰고도 에러 전달 가능
- 대략 적인 순서
- 예외를 던지면 예외 객체(프로그래머가 정의 가능) 자료구조에 추가
- try-catch 블록에 도달할 때까지 콜 스택을 자동으로 펼침
- try-catch 블록 찾으면 예외 객체를 처리할 수 있는 catch 블록을 찾아 실행
- 단점은 프로그램 성능 저해
- try-catch 블록을 포함하는 모든 함수의 스택 프레임에는 스택 펼치기(stack unwinding)를 위한 별도의 정보를 추가 해야 함
- 프로그램 단 한 부분에서만 사용해도 프로그램 전체를 예외 처리 사용하게 설정 해야 함
- 예외 사용하는 라이브러리 사용하면서 게임 코드는 예외 처리 사용 안하려면?
- 격리
- 해당 라이브러리 API 호출을 새로운 함수로 감싸고 이 함수들이 구현된 번역 단위에는 예외 처리를 활성화
- 이 함수들은 발생 가능 한 모든 예외에 대해 try-catch 블록으로 감싸 처리 그 결과로 에러 코드 리턴
- 제일 문제는 goto 와 별반 다를 게 없으며 더 나쁠지도 - 조엘 스폴스키
- 모든 예외 상황을 고려하고 맞게 처리 하지 않는다면 비정상적인 상태에 빠질 가능성이 있음
- 컴파일러는 코드 크기를 증가 시키고 이로 인해 캐시 성능이 떨어 질 수 있고 인라인 처리 될 것도 안될 가능 성이 있음
- 게임 엔진 단에서는 예외처리리를 사용하지 않는 것이 유리
Assertion
- 어떤 표현을 검사하는 코드 : 표현이 거짓일 경우 프로그램 중단 후 메시지 출력
- 프로그래머가 가정한 조건을 점검하는 역할
- 버그 발생시 확실하게 알려주도록 하여 이 타이밍에 고치는게 최적
- 개발단계에는 별 문제 없지만, 릴리즈에서는 성능을 위해 코드를 제거
-
내 assert() 매크로는 DEBUG 전처리기 심볼 유효 시 동작
-
- 엔진에서는 좀 더 세밀하게 조절 필요
- UNREAL -> Debug / Debug Game / Development / Test / Shipping
구현
#if ASSERTIONS_ENABLED
#define debugBreak() { __asm int 3; } // CPU 마다 다를 수 있음
#define ASSERT(expr) \
{ \
if (expr) {} \
else { \
report(#expr, __FILE__, __LINE__); \
debugbreak(); \
} \
}
#else
#define ASSERT(expr)
#endif
- 코드에 Assertion 을 적극 사용하는 것은 좋음
- 잘 사용하는 것도 매우 중요
- 실패하면 무조건 실행 정지
- 치명적 에러를 잡는데 사용해야 함
컴파일시
- 컴파일 타이밍에 에러 검출
- C++11 부터는 static_assert() 지원
- 지원이 안될 경우는 직접 구현
#define _ASSERT_GLUE(a, b) a ## b
#define ASSERT_GLUE(a, b) _ASSERT_GLUE(a, b)
#define STATIC_ASSERT(expr) enum { ASSERT_GLUE(g_assert_fail_, __LINE__) = 1 / (int)(!!(expr)) };
STATIC_ASSERT(sizeof(int) == 4); // 성공
STATIC_ASSERT(sizeof(float) == 1); // 실패
- 템플릿을 활용한 구현
#define _ASSERT_GLUE(a, b) a ## b
#define ASSERT_GLUE(a, b) _ASSERT_GLUE(a, b)
#ifdef __cplusplus
#if __cplusplus >= 201103L
#define STATIC_ASSERT(expr) static_assert(expr, "static assert failed:" #expr)
#else
template<bool> class TStaticAssert;
template<> class TStaticAssert<true> {};
#define STATIC_ASSERT(expr) enum { ASSERT_GLUE(g_assert_fail_, __LINE__) = sizeof(TStaticAssert<!!(expr)>) };
#endif
#endif
STATIC_ASSERT(sizeof(int) == 4); // 성공
STATIC_ASSERT(sizeof(float) == 1); // 실패