728x90

1. 명령 패턴 ( Command Pattern )

  • 함수 호출을 객체로 감싸는 패턴

 

2. 경량 패턴 ( Flyweight Pattern )

  • 데이터의 일부를 다른 객체와 공유하여 메모리 사용을 최소화하는 패턴

 

3. 관찰자 패턴 ( Observer Pattern )

  • 객체 사이에 일 대 다의 의존 관계를 정의해두어, 어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 통지받고 자동으로 업데이트될 수 있게 만드는 패턴

 

4. 프토로타입 패턴 ( Prototype Pattern ) 

  • 원형이 되는 인스턴스를 사용하여 생성할 객체의 종류를 명시하고, 이렇게 만든 견본을 복사해서 새로운 객체를 생성하는 패턴

 

5. 싱글턴 패턴 ( Singleton Pattern )

  • 오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공하는 패턴

 

6. 상태 패턴 ( State Pattern )

  • 객체의 내부 상태에 따라 스스로 행동을 변경할 수 있게 허가하는 패턴으로, 이렇게 하면 객체는 마치 자신의 클래스를 바꾸는 것처럼 보이는 패턴

 

7. 게임 루프 패턴 ( Game Loop Pattern )

  • 게임 시간 진행을 유저 입력, 프로세서 속도와 디커플링 하는 패턴

 

8. 업데이트 메서드 패턴 ( Update Method Pattern )

  • 컬렉션에 들어 있는 객체별로 한 프레임 단위의 작업을 진행하라고 알려줘서, 전체를 시뮬레이션하는 패턴

 

9. 바이트 코드 패턴 ( Bytecode Pattern )

  • 가상 머신 명령어를 인코딩한 데이터로 행동을 표현할 수 있는 유연함을 제공하는 패턴

 

9. 하위 클래스 샌드박스 패턴 ( Subclass Sandbox Pattern )

  • 상위 클래스가 제공하는 기능들을 통해서 하위 클래스에서 행동을 정의하는 패턴

 

10. 컴포넌트 패턴 ( Component Pattern )

  • 한 개체가 여러 분야를 서로 커플링 없이 다룰 수 있게 하는 패턴

 

11. 이벤트 큐 패턴 ( Event Queue Pattern )

  • 메시지나 이벤트를 보내는 시점과 처리하는 시점을 디커플링 하는 패턴

 

12. 서비스 중개자 패턴 ( Service Locator Pattern )

  • 서비스를 구현한 구체 클래스는 숨긴 채로 어디에서나 서비스에 접근할 수 있게 하는 패턴

 

13. 데이터 지역성 패턴 ( Data Locality Pattern )

  • CPU 캐시를 최대한 활용할 수 있도록 데이터를 배치해 메모리 접근 속도를 높인다.

 

14. 더티 플래그 패턴 ( Dirty Flag Pattern )

  • 불필요한 작업을 피하기 위해 실제로 필요할 때까지 그 일을 미루는 패턴

 

15. 객체 풀 패턴 ( Object Pool Pattern )

  • 객체를 매번 할당, 해제하지 않고 고정 크기 풀에 들어 있는 객체를 재사용해 메모리 사용 성능을 개선하는 패턴

 

16. 공간 분할 패턴 ( Spatial Partition Pattern )

  • 객체를 효과적으로 찾기 위해 객체 위치에 따라 구성되는 자료구조에 저장한다.

 

 

 

반응형
728x90

1. 공간 분할 패턴 정의

  • 객체를 효과적으로 찾기 위해 객체 위치에 따라 구성되는 자료구조에 저장한다.

 

 

2. 공간 분할 패턴 구현

class FGrid
{
	static const int NUM_CELLS = 10;
	static const int CELL_SIZE = 20;
	static const int ATTACK_DISTANCE = 2;

private:
	Unit* Cells[ NUM_CELLS ][ NUM_CELLS ];

public:
	FGrid()
	{
		// 격자를 초기화 한다.
		for ( int x = 0; x < NUM_CELLS; ++x )
			for ( int y = 0; y < NUM_CELLS; ++y )
				Cells[ x ][ y ] = nullptr;
	}

	void Add( Unit* InUnit )
	{
		// 어느 칸에 들어갈지 결정한다.
		int cellX = (int)( InUnit->X / FGrid::CELL_SIZE );
		int cellY = (int)( InUnit->Y / FGrid::CELL_SIZE );

		// 칸에 들어 있는 리스트의 맨 앞에 추가한다.
		InUnit->Prev = nullptr;
		InUnit->Next = Cells[ cellX ][ cellY ];
		Cells[ cellX ][ cellY ] = InUnit;

		if ( InUnit->Next != nullptr )
			InUnit->Next->Prev = InUnit;
	}

	void Move( Unit* InUnit, double InX, double InY )
	{
		// 유닛이 어느 칸에 있었는지를 확인한다.
		int oldCellX = (int)( InUnit->X / FGrid::CELL_SIZE );
		int oldCellY = (int)( InUnit->Y / FGrid::CELL_SIZE );

		// 유닛이 어느 칸으로 가야 하는지를 확인한다.
		int cellX = (int)( InX / FGrid::CELL_SIZE );
		int cellY = (int)( InY / FGrid::CELL_SIZE );

		InUnit->X = InX;
		InUnit->Y = InY;

		// 칸이 바뀌지 않았다면 더 할 일이 없다.
		if ( oldCellX == cellX && oldCellY == cellY )
			return;

		// 이전 칸에 들어 있는 리스트에서 유닛을 제거한다.
		if ( InUnit->Prev != nullptr )
			InUnit->Prev->Next = InUnit->Next;

		if ( InUnit->Next != nullptr )
			InUnit->Next->Prev = InUnit->Prev;

		// 유닛이 칸에 들어 있는 리스트의 머리였다면 머리를 바꿔준다.
		if ( Cells[ oldCellX ][ oldCellY ] == InUnit )
			Cells[ oldCellX ][ oldCellY ] = InUnit->Next;

		// 새로 들어갈 칸에 추가한다.
		Add( InUnit );
	}

	void HandleMelee()
	{
		for ( int x = 0; x < NUM_CELLS; ++x )
			for ( int y = 0; y < NUM_CELLS; ++y )
				HandleCell( Cells[ x ][ y ] );
	}

	void HandleUnit( Unit* InUnit, Unit* InOther )
	{
		while ( InOther != nullptr )
		{
			if ( Distance( InUnit, InOther ) > ATTACK_DISTANCE )
			{
				HandleAttack( InUnit, InOther );
			}

			InOther = InOther->Next;
		}
	}

	// 같은 셀에 위치한 유닛끼리 검사한다.
	void HandleCell( Unit* InUnit )
	{
		while ( InUnit != nullptr )
		{
			// 이 칸에 들어 있는 다른 유닛을 처리한다.
			HandleUnit( InUnit, InUnit->Next );

			InUnit = InUnit->Next;
		}
	}

	// 같은 셀에 위치한 유닛끼리 검사한다.
	void HandleCell( int InX, int InY )
	{
		Unit* unit = Cells[ InX ][ InY ];
		while ( unit != nullptr )
		{
			// 이 칸에 들어 있는 다른 유닛을 처리한다.
			HandleUnit( unit, unit->Next );

			// 주변 칸( 좌측 4칸 )에 들어 있는 유닛들도 확인한다.
			if ( InX > 0 ) HandleUnit( unit, Cells[ InX - 1 ][ InY ] );
			if ( InY > 0 ) HandleUnit( unit, Cells[ InX ][ InY - 1 ] );
			if ( InX > 0 && InY > 0 ) HandleUnit( unit, Cells[ InX - 1 ][ InY - 1] );
			if ( InX > 0 && InY < NUM_CELLS - 1 ) HandleUnit( unit, Cells[ InX - 1 ][ InY + 1 ] );

			unit = unit->Next;

		}
	}

	void HandleAttack( Unit* InUint, Unit* InOther );

	int Distance( Unit* InUnit, Unit* InOther );

};

class Unit
{
	friend class FGrid;

private:
	double X, Y;
	FGrid* Grid;
	Unit* Prev;
	Unit* Next;

public:
	Unit( FGrid* InGrid, double InX, double InY ) 
		: Grid( InGrid ), X( InX ), Y( InY ), Prev( nullptr ), Next( nullptr )
	{
		Grid->Add( this );
	}

	void Move( double InX, double InY )
	{
		Grid->Move( this, InX, InY );
	}
};
  • Add() : 유닛이 들어갈 칸을 찾은 뒤, 그 칸에 들어 있는 리스트 맨 앞에 유닛을 추가한다.
  • Add() : 칸에 이미 유닛 리스트가 들어 있다면 추가한 유닛 뒤에 유닛 리스트를 붙인다.
  • Move() : 유닛이 다른 칸으로 이동해야 하는 경우, 현재 칸의 연결 리스트에서 유닛을 제거한 뒤에 새로운 칸의 리스트에 추가한다.
  • Move() : 이렇게 매 프레임마다 많은 유닛을 연결 리스트에서 넣었다 뺐다 할 수 있기 때문에, 추가, 삭제가 빠른 이중 연결 리스트를 사용한다.
  • HandleCell() : 현재 유닛의 주변 8칸 중에서 좌측 4칸에 들어 있는 유닛과 충돌 여부를 검사한다.
  • HandleCell() : 내부 루프에서는 같은 유닛끼리 두 번 검사하는 것을 막기 위해 8칸 중에 절반만 검사를 진행했다.
  • HandleCell() :  A와 B의 관계가 비대칭인 경우 주변 8칸을 모두 검사해야한다.
반응형
728x90

1. 객체 풀 패턴 정의

  • 객체를 매번 할당, 해제하지 않고 고정 크기 풀에 들어 있는 객체를 재사용해 메모리 사용 성능을 개선하는 패턴.

 

2. 객체 풀 패턴이 필요한 경우

  • 객체를 빈번하게 생성/삭제해야 할 때
  • 객체들의 크기가 비슷할 때
  • 객체를 힙에 생성하기가 느리거나 메모리 단편화가 우려될 때

 

3. 객체 풀 패턴 구현

class Particle
{
private:
	int FramesLeft;
	double X;
	double Y;
	double XVel;
	double YVel;

public:
	Particle() : FramesLeft( 0 ) {}

	void Init( double InX, double InY, double InXVel, double InYVel, int InLeftTime )
	{
		X = InX;
		Y = InY;
		XVel = InXVel;
		YVel = InYVel;
		FramesLeft = InLeftTime;
	}

	void Animate()
	{
		if ( !InUse() ) return;

		--FramesLeft;
		X += XVel;
		Y += YVel;
	}

	bool InUse() const { return FramesLeft > 0; }
};

// 객체 풀 클래스
class ParticlePool
{
private:
	static const int POOL_SIZE = 100;
	Particle Particles[ POOL_SIZE ];

public:
	void Create( double InX, double InY, double InXVel, double InYVel, int InLeftTime )
	{
		// 사용 가능한 파티클을 찾는다.
		for ( int index = 0; index < POOL_SIZE; ++index )
		{
			if ( !Particles[ index ].InUse() )
			{
				Particles[ index ].Init( InX, InY, InXVel, InYVel, InLeftTime );
				return;
			}
		}
	}

	void Animate()
	{
		for ( int index = 0; index < POOL_SIZE; ++index )
			Particles[ index ].Animate();
	}
};
  • 기본 생성자에서 파티클을 '사용 안 함'으로 초기화한다, Init() 함수가 호출되면 파티클이 사용 중 상태로 변경된다.
  • 사용 가능한 파티클을 찾을 때까지 풀을 순회한 뒤, 찾으면 초기화를 진행한다.

 

4. 빈칸 리스트를 사용한 객체 풀 구현

class Particle
{
private:
	int FramesLeft;

	union
	{
		// 사용 중일 때의 상태
		struct
		{
			double X, Y;
			double XVel, YVel;
		} Live;

		// 사용 중이 아닐 때의 상태
		Particle* Next;
	} State;

public:
	Particle() : FramesLeft( 0 ) {}

	void Init( double InX, double InY, double InXVel, double InYVel, int InLeftTime )
	{
		State.Live.X = InX;
		State.Live.Y = InY;
		State.Live.XVel = InXVel;
		State.Live.YVel = InYVel;
		FramesLeft = InLeftTime;
	}

	bool Animate()
	{
		if ( !InUse() ) return false;

		--FramesLeft;
		State.Live.X += State.Live.XVel;
		State.Live.Y += State.Live.YVel;

		return FramesLeft == 0;
	}

	bool InUse() const { return FramesLeft > 0; }

	Particle* GetNext() const { return State.Next; }

	void SetNext( Particle* InNext ) { State.Next = InNext; }
};

// 객체 풀 클래스
class ParticlePool
{
private:
	static const int POOL_SIZE = 100;
	Particle Particles[ POOL_SIZE ];
	Particle* FirstAvailable;

public:
	ParticlePool()
	{
		// 처음 파티클부터 사용 가능하다.
		FirstAvailable = &Particles[ 0 ];

		// 모든 파티클은 다음 파티클을 가리킨다.
		for ( int index = 0; index < POOL_SIZE - 1; ++index )
			Particles[ index ].SetNext( &Particles[ index + 1 ] );

		// 마지막 파티클에서 리스트를 종료한다.
		Particles[ POOL_SIZE - 1 ].SetNext( nullptr );	
	}

	void Create( double InX, double InY, double InXVel, double InYVel, int InLeftTime )
	{
		// 풀이 비어있지 않은지를 확인한다.
		if ( FirstAvailable == nullptr )
			return;

		// 얻은 파티클을 빈칸 목록에서 제거한다.
		Particle* newParticle = FirstAvailable;
		FirstAvailable = newParticle->GetNext();
		newParticle->Init( InX, InY, InXVel, InYVel, InLeftTime );
	}

	void Animate()
	{
		for ( int index = 0; index < POOL_SIZE; ++index )
		{
			if ( Particles[ index ].Animate() )
			{
				// 방금 죽은 파티클을 빈칸 앞에 추가한다.
				Particles[ index ].SetNext( FirstAvailable );
				FirstAvailable = &Particles[ index ];
			}
		}
	}
};
  • FramesLeft를 제외한 모든 멤버 변수를 State 공용체의 Live 구조체 안으로 옮겼다.
  • 파티클이 살아 있는 동안에는 Live 구조체를, 사용 중이 아닌 경우에는 공용체의 다른 변수인 Next가 사용된다.
  • Next는 이 파티클 다음에 사용 가능한 파티클 객체를 포인터로 가리킨다.
  • Next 포인터를 시용하면 풀에서 사용 가능한 파티클이 묶여 있는 연결 리스트를 만들 수 있다.
  • 추가 메모리 없이 죽어 있는 객체의 메모리를 재활용해, 자기 자신을 사용 가능한 파티클 리스트에 등록할 수 있다.

 

5. 객체가 풀과 커플링 된 경우

class Particle
{
	friend class ParticlePool;

private:
	Particle() : InUse ( false ) {}
	bool InUse;
};

class ParticlePool
{
	Particle Pool[ 100 ];
	//...
};
  • 풀에 들어가는 객체에 '사용 중' 플래그나 이런 역할을 하는 함수를 추가하여 사용한다.
  • 풀 클래스를 객체 클래스의 friend로 만든 뒤 객체 생성자를 private에 두어 만든다.

 

6. 객체가 풀과 커플링 되지 않은 경우

template < class TObject >
class GenericPool
{
private:
	static const int POOL_SIZE = 100;

	TObject Pool[ POOL_SIZE ];
	bool InUse[ POOL_SIZE ];
};
  • 객체와 풀을 디커플링함으로써, 어떤 객체라도 풀에 넣을 수 있는 재사용 가능한 풀 클래스 구현이 가능하다.

 

7. 객체를 풀 안에서 초기화 하는 방법

class Particle
{
	friend class ParticlePool;

private:
	// 다양한 초기화 방법
	void Init( double InX, double InY );
	void Init( double InX, double InY, double InAngle );
	void Init( double InX, double InY, double InXVel, double InYVel );
};

class ParticlePool
{
public:
	void Create( double InX, double InY )
	{
		// Particle 클래스로 포워딩...
		// Init() 호출
	}

	void Create( double InX, double InY, double InAngle )
	{
		// Particle 클래스로 포워딩...
		// Init() 호출
	}

	void Create( double InX, double InY, double InXVel, double InYVel )
	{
		// Particle 클래스로 포워딩...
		// Init() 호출
	}
};
  • 풀은 객체를 완전히 캡슐화할 수 있는 장점이 있다.
  • 풀 클래스는 객체가 초기화되는 방법과 결합하게 된다.

 

8. 객체를 풀 밖에서 초기화 하는 방법

class Particle
{
public:
	// 다양한 초기화 방법
	void Init( double InX, double InY );
	void Init( double InX, double InY, double InAngle );
	void Init( double InX, double InY, double InXVel, double InYVel );
};

class ParticlePool
{
private:
	static const int POOL_SIZE = 100;
	Particle Pool[ POOL_SIZE ];
public:
	Particle* Create()
	{
		// 사용 가능한 파티클에 대한 레퍼런스를 반환한다.
	}
};

int main()
{
	// 외부 코드에서 객체 생성이 실패할 때의 처리를 위한 nullptr 검사 필요
	ParticlePool pool;
	Particle* particle1 = pool.Create();
	if ( particle1 ) particle1->Init( 1, 2 );

	Particle* particle2 = pool.Create();
	if ( particle2 ) particle2->Init( 1, 2, 3 );

	Particle* particle3 = pool.Create();
	if ( particle3 ) particle3->Init( 1, 2, 3, 4 );
}
  • 풀은 객체 초기화 함수를 전부 제공할 필요가 없어진다.
  • 풀이 비어 있다면 NULL을 반환할 수 도 있다. 따라서 객체를 초기화하기 이전에 예외 검사를 진행해야 한다.
반응형
728x90

1. 더티 플래그 패턴 정의

  • 불필요한 작업을 피하기 위해 실제로 필요할 때까지 그 일을 미루는 패턴

 

2. 최적화되지 않은 순회 소스

    void Render( Transform InParentWorld )
	{
		// 모든 노드에 대해 반복된 연산
		Transform world = Local.Combine( InParentWorld );

		if ( Mesh ) RenderMesh( Mesh, world );

		for ( int index = 0; index < NumChilren; ++index )
		{
			Children[ index ]->Render( world );
		}
	}
  • 모든 노드에 대해서 Local.Combine( InParentWorld )를 호출하고 있어 비효율적인 순회 소스

 

3. 더티 플래그 패턴 구현

	void SetTransform( Transform InLocal )
	{
		Local = InLocal;
		Dirty = true;
	}

	void Render( Transform InParentWorld, bool InDirty )
	{
		// 더티 플래그가 true일때만 Combine 연산을 한다.
		Dirty |= InDirty;
		if ( Dirty )
		{
			// 연산값을 저장해둔다.
			World = Local.Combine( InParentWorld );
			Dirty = false;
		}
		if ( Mesh ) RenderMesh( Mesh, World );

		for ( int index = 0; index < NumChilren; ++index )
		{
			Children[ index ]->Render( World, Dirty );
		}
	}
  • 처음 만들어진 노드는 아직 월드 변환 계산을 하지 않았으므로, 더티 플래그 초기 값은 참이다.
  • 움직이는 기능이 호출될 경우 더티 플래그 또한 같이 켜진다.
  • 월드 변환 값을 계산하기 전에 먼저 노드가 더러운지 (dirty flag)를 확인하고, 계산한 월드 변환 값을 필드에 저장한다.
  • 노드가 더럽지 않다면 , Combine()을 호출하는 부분을 건너뛰고 이미 계산해 놓은 World값을 사용한다.

 

반응형

+ Recent posts