객체를 매번 할당, 해제하지 않고 고정 크기 풀에 들어 있는 객체를 재사용함으로써 메모리 사용 성능을 개선한다.
#include <cassert>
class Particle {
public:
void Init(double x, double y, double xVel, double yVel, int lifetime);
bool Animate();
bool InUse() const { return framesLeft_ > 0; }
Particle* GetNext() const { return state_.next; }
void SetNext(Particle* next) { state_.next = next; }
private:
int framesLeft_ = 0;
union {
//사용 중일 때의 상태
struct{
double x, y;
double xVel, yVel;
} live;
//사용 중이 아닐 때의 상태
Particle* next;
} state_;
};
void Particle::Init(double x, double y, double xVel, double yVel, int lifetime) {
state_.live.x = x;
state_.live.y = y;
state_.live.xVel = xVel;
state_.live.yVel = yVel;
framesLeft_ = lifetime;
}
bool Particle::Animate() {
if (!InUse()) return;
--framesLeft_;
state_.live.x += state_.live.xVel;
state_.live.y += state_.live.yVel;
return framesLeft_ == 0;
}
//빈칸 리스트 기법 : 사용할 때 리스트에서 꺼내오고 사용끝나면 리스트에 추가한다.(객체 재사용하여 메모리 할당/헤제줄이기)
class ParticlePool {
public:
ParticlePool() {}
void Create(double x, double y, double xVel, double yVel, int lifetime);
void Animate();
private:
static const int POOL_SIZE = 100;
Particle particles_[POOL_SIZE];
//헤드
Particle* firstAvailable_;
};
ParticlePool::ParticlePool() {
//처음 파티클부터 사용 가능하다.
firstAvailable_ = &particles_[0];
//모든 파티클은 다음 파티클을 가리킨다.
for (int i = 0; i < POOL_SIZE - 1; i++){
particles_[i].SetNext(&particles_[i + 1]);
}
//마지막 파티클에서 리스트를 종료한다.
particles_[POOL_SIZE - 1].SetNext(nullptr);
}
void ParticlePool::Create(double x, double y, double xVel, double yVel, int lifetime){
//풀이 비어 있지 않은 지를 확인한다.
assert(firstAvailable_ != nullptr);
//얻은 파티클을 빈칸 목록에서 제거한다.
Particle* newParticle = firstAvailable_;
firstAvailable_ = newParticle->GetNext();
newParticle->Init(x, y, xVel, yVel, lifetime);
}
void ParticlePool::Animate(){
for (int i = 0; i < POOL_SIZE; i++){
if (particles_[i].Animate()) {
// 방금 죽은 파티클을 빈칸 리스트 앞에 추가한다.
particles_[i].SetNext(firstAvailable_);
firstAvailable_ = &particles_[i];
}
}
}