0. 도입
- 좋은 소프트웨어 구조란, 수정이 쉽고 직관적인 구조
- 프로토타이핑은 기능을 빠르게 보는데 도움을 주지만 유지가 힘듦
- 버릴 코드는 반드시 버려야 함
- 버릴 코드에 시간 쓰지 말기
- 깔끔한 구조, 최적화, 빠른 구현 중 적절히 비중을 두어 개발해야함
- 세마리 토끼 다 잡기 없음
- 유연할 필요가 없다면 디커플링은 오버 엔지니어링임
- 저수준 핵심 최적화는 가능하면 늦게 작업
1. 디자인 패턴
Command Pattern
- method 호출을 데이터, 즉 객체화한 것
- 변수에 저장하거나, 함수에 전달할 수 있음
-
callback, 함수 포인터, 클로저, 부분 적용 함수…
- example
- Closure를 사용하여 함수형으로 구현 가능
first-class object
(일급 객체: 대부분의 연산을 지원)를 사용- 대상을 객체로 전달받으면 제어의 대상을 편하게 변경할 수 있음 (AI 구현에 용이)
- 명령을 queue나 stream으로 관리
- producer와 consumer를 디커플링할 수 있음
- undo 가능
경량 패턴
- sharing을 통해 많은 수의 fine-grained 객체를 지원
- 객체를 마구 늘리지 않고 객체지향의 장점만 취할 수 있음
-
pointer를 통해서 객체의 값을 얻으려고 하면 캐시 미스가 발생할 수 있음
- example
- GPU에 같은 모양의 오브젝트들을 렌더링하기 위한 정보(메시)를 1회만 보내고 남은 정보(위치)만을 개별 처리함
Observer
- 보고받는 대상을 지정하지 않고 이벤트를 보고하는 패턴
- Observer class가 알림을 기다림
- 관찰이 되는 대상이 이벤트 발생 시, Observer class에 알림을 줌
- 대상은 옵저버를 복수개 들고 있음
- 동기적인 함수 호출
- example
- C# event keyword가 지원: dotnet observer
Prototype
- 원형이 되는 인스턴스를 사용하여 객체 종류를 명시, 견본을 복사해 새로운 객체를 생성
- class기반 언어에서 객체에서는 일부를 상위 객체에 위임할 수 있음 (상속)
- JS는 복제없는 프로토타입 기반 언어
- JSON 데이터를 작성할 때, prototype을 사용하면 중복되는 데이터양을 줄일 수 있음
Singleton
- 오직 하나의 클래스 인스턴스만 갖도록 보장하는 패턴
- 전역 접근을 제공
- 코드를 이해하기 힘들게 만들고, 커플링을 조장함
- 싱글턴 상속: 여러 플랫폼을 지원해야할 때, 플랫폼 별로 클래스를 만들 수 있음
- 정적 플래그를 써서 인스턴스 수를 제한할 수 있음
상태
- 객체 내부 상태에 따라 스스로 행동을 변경할 수 있게 허가하는 패턴
- finite state machine
- 가질 수 있는 상태는 한정되며, 입력이나 이벤트에 의해 변경됨
- 계층형 상태 기계: state machine의 각 상태는 상태를 상속할 수 있음
2. 순서 패턴
이중 버퍼
- 그래픽스 랜더링에서 사용
- 하나의 버퍼를 쓰는 시간이 오래 걸리는 상황에서, 언제나 온전한 버퍼를 필요로 함
- 순차적으로 변경해야하며, 변경 도중 접근이 가능해야하는 상황에 용이
- 한 프레임에 가해진 상태가 그 프레임동안 유지되어야 하는 상황에 용이
- 작업 중인 상태에는 접근할 수 없어야함
- 교체에 비용이 들고, 메모리가 배로 필요
Game loop
int App::OnExcute() {
if (OnInit() == false) {
return -1;
}
SDL_Event event;
while (isRunning) {
while (SDL_PollEvent(&event)) {
// 이벤트 처리
OnEvent(&event);
}
// 게임 업데이트
OnLoop();
// 렌더링
OnRender();
}
OnCleanUp();
return 0;
}
- 한 번 Loop를 돌 때마다 유저 입력을 처리한 뒤 게임 상태를 업데이트, 화면을 렌더링하는 루프
- HW에 상관없이 일정한 속도의 실행을 보장해야함
- 전 프레임에 걸린 시간에 따라 다음 프레임 시간을 유동적으로 조정할 수 있음
- 실제 시간에 기반하여 연산함
- 시간 간격에 예민한 렌더링만 고정 프레임 시간을 사용할 수 있음
- 렌더링 타이밍과 게임 업데이트 타이밍을 잘 비교해야함
Update method
- 각 객체에 프레임 단위의 작업 진행하라고 알림
- 가변시간 Loop를 사용한다면 작업 진행과 렌더링에 걸린 시간 간격을 알려야함
- 유니티의 MonoBehaviour에서 사용함: MonoBehaviour
Update
: 한 프레임 간격으로 실행됨 (TIme.deltaTime: 전 프레임 업데이트 후로부터 지난 시간)FixedUpdate
: 고정된 프레임 레이트 간격으로 실행됨 (시간이 고정됨)
3. 행동 패턴
Byte code
- virtual machine 명령어를 인코딩한 데이터
- 행동을 데이터로 정의하고 코드는 그것을 읽기만 하는 방식
- 행동을 스택으로 관리하여 실행하는 방식이 있음
하위 클래스 sandbox
- 상위 클래스가 제공하는 기능을 통해서 하위 클래스 행동 정의
- 상속 이점
- 하위 클래스들의 겹치는 행동을 공유할 수 있음
- 초기화 상위 클래스 상태를 하위 클래스로부터 지키고 싶은 경우
- 생성자에 상위 클래스 초기화에 관련된 인자를 넘기지 않고 별도의 Init 메소드를 추가함
- 모든 클래스에서 같은 상태를 쓴다면 정적 멤버로 만들면 쉬움
- 직접 생성자에서 접근하여 참조할 객체를 가져올 수 있음
type object
- 타입 객체 인스턴스는 논리적으로 서로 다름
- 타입 사용 객체는 자신의 타입을 나타내는 타입 객체를 참조함
4. 디커플링 패턴
Component
- 커플링을 줄일 수 있는 패턴
- 컴포넌트는 하나의 분야를 담당하고, 한 개체는 여러 컴포넌트를 사용함으로 여러 일을 커플링없이 처리할 수 있음
- 컴포넌트 끼리의 통신
- 컨테이너 객체를 거쳐서 전달
- 컨테이너에게 메시지를 전달하면, 컨테이너가 모든 컴포넌트에 메시지 전달
Event queue
- 이벤트를 보내는 시점과 처리하는 시점을 디커플링함
- 단순 호출부와 실행부 분리는 옵저버 패턴으로 커버 가능
- 원형 버퍼에 저장하면 큐 앞에서부터 순차적으로 이벤트를 가져올 수 있어 좋음
service provider
- 일종의 싱글턴 패턴
- 인스턴스가 요청되는 순간에 반드시 인스턴스를 가지고 있어야함
- null인 경우 반환할 아무 일을 하지 않는 null 객체를 반환
- 로그 데커레이터
- 서비스 제공자를 필요에 따라 래핑하여 사용
- 비활성화를 원하는 기능은 널 객체로 서비스를 전환시켜두면 됨
5. 최적화 패턴
데이터 지역성
- CPU 캐시 히트를 최대로 하기 위해 적절한 자료구조 선정이 필요함
더티 플래그
- 값이 전과 다름을 알리는 플래그
- 상위 상태에 의존적인 상태를 가지고 있는 하위 상태의 값 계산을 뒤로 미룰 수 있음
- 하이라키상 가장 상위에 있는 객체의 위치가 바뀌면 상위 객체부터 하위 객체까지 반복적인 위치 계산이 이루어짐 -> 낭비
객체 풀
- 반복된 할당 해제로 인한 메모리 단편화를 방지하기 위함
- 미리 사용할 객체를 생성하고 필요에 따라 객체를 제공하는 패턴
- 사용 중이지 않은 객체가 메모리에 남아있음