동작 명령 대기열


개요

명령 대기열(command Queue)은 큐 기반 구조(FIFO)를 사용해 매 프레임마다 명령을 수집하고 일관되게 처리하는 디자인 패턴입니다. 이 방식은 게임 시스템과 오브젝트의 동작을 분리하며, 게임 로직을 큐에 쌓인 명령 중심으로 구성해 동작을 깔끔하고 유연하게 제어할 수 있도록 돕습니다. (적용 사례: D3D12 기반 비행 데모)

⚠️ 참고: 이 패턴은 구조화와 모듈화를 개선하지만, 지연 실행으로 인한 오버헤드가 발생할 수 있고, 명령이 잘못 전달되거나 예상치 않게 지연되면 디버깅이 어려워질 수 있습니다.

저장소 보기


구조 설계도

  • 흰색 테두리: 처리 흐름
  • 노란색 테두리: 명령들 집합체

클래스 목록 및 설명

Command 구조체 - 코드 분석

#pragma once
#include <functional>
 
struct FCommand
{
	using Action = std::function<void(float)>;
 
	Action action;
};
float deltaTime을 매개변수로 받는 호출 가능한 action(주로 람다나 함수 객체)을 래핑합니다. 이를 통해 명령이 시간 의존적인 로직을 캡슐화하고 처리할 수 있습니다.


CommandQueue 클래스 - 코드 분석

#pragma once
#include <queue>
#include "Command.h"
 
class CCommandQueue
{
public:
	CCommandQueue() = default;
	~CCommandQueue() = default;
 
private:
	std::queue<FCommand> mQueue;
 
public:
	void Enqueue(const FCommand& command)
	{
		mQueue.push(command);
	}
 
	FCommand Dequeue()
	{
		if (IsEmpty())
			return FCommand{};
 
		FCommand cmd = mQueue.front();
		mQueue.pop();
 
		return cmd;
	}
 
	bool IsEmpty() const
	{
		return mQueue.empty();
	}
};
CCommandQueueFCommand 객체를 FIFO 방식으로 관리하고 실행을 처리합니다. 명령 생성과 실행 시점을 분리하여, 게임 로직을 더 깔끔하고 모듈화된 형태로 구성할 수 있습니다.


코드 분석

사용 예제

#define FakeDeltaTime 0.016f
 
int main()
{
	CScene*   scene   = new CScene;
	CPlayer*  player  = new CPlayer;
	CMonster* monster = new CMonster;
 
	scene->AddObject(player);
	scene->AddObject(monster);
 
	// This simulates 5 frames, pushing and executing one command per frame.
	for (int frame = 1; frame <= 5; frame++)
	{
		std::cout << ">> [Frame " << frame << "] <<\n";
 
		FCommand cmd;
		cmd.action = [player, monster, frame](float dt)
		{
			if (frame % 2 == 0)
				player->PlayerAction(dt);
			else
				monster->MonsterAction(dt);
		};
		scene->PushCommand(cmd);
		
		scene->Update(FakeDeltaTime);
 
		std::cout << "\n";
	}
	delete scene;
 
	return 0;
}
이 예제는 player, monster, 그리고 frame을 캡처하는 람다를 사용해 각 프레임마다 명령을 생성하는 5프레임 처리 과정을 수행합니다. 생성된 각 명령은 PushCommand()를 통해 scene에 추가하고, Scene::Update()에서 실행됩니다.


CScene::Update()

void CScene::Update(float deltaTime)
{
	// Process all pending commands
	while (!mCommandQueue.IsEmpty())
	{
		FCommand cmd = mCommandQueue.Dequeue();
		if (cmd.action)
			cmd.action(deltaTime);
	}
}
이 함수는 mCommandQueue에서 대기 중인 모든 명령을 순서대로 제거하며 실행합니다.


맺는 말

명령 대기열은 로직과 실행을 명확히 분리하여 프레임 기반 시스템을 더욱 모듈화하고 유지 보수성을 높이는 동시에 구조화와 타이밍 제어를 개선, 일괄 처리 최적화 같은 장점을 가질 수도 있습니다.

반면, 지연 실행으로 인한 오버헤드가 발생하고 명령의 순서 관리나 디버깅이 복잡해질 수 있으며 명령 대기열에 명령을 계속 보관하는 경우 메모리 사용량이 증가할 수도 있는 단점이 있습니다.

명령 대기열은 이러한 장단점을 고려해, 적용 여부를 신중히 결정해야 합니다.