GitHub : https://github.com/developeSHG/CPP20-4th_Emperor/commit/19ff2673e96a446bee711c99873b180d69b52391
Module 기능과 함께 가장 중요한 기능인 Coroutine
// 유니티 = 코루틴
// - C#에서 있는 코루틴 문법을 유니티에서 적극적으로 채용해서 제공
어떤 함수를 호출할 때, 함수를 어디까지 진행했는지 저장하고, 일시정지했다가 다시 이어서 호출할 수 있는 기능
간단히 말해서 내가 원할 때, 핸들을 이용해 코드를 다시 재게하거나 제거해버리는 동작을 하는 게 코루틴 문법.
// 몬스터 잡으면 -> 아이템이 인벤토리에 들어온다.
// 1. 몬스터를 잡음
// 2. DB처리와 아이템 생성 동시에 적용 (DB 처리시간 단축)
void KillMonster()
{
// DB에 아이템 생성 요청
// -- DB 처리 -- (DB 처리는 굉장히 오래걸림)
// 아이템 생성
// 인벤토리에 아이템 추가
}
이렇게 되었을 때, 만약 DB처리가 실패했을 경우, DB세계와 실제세계가 달라서 아이템 복사가 발생하는 등 메모리가 불일치한 큰 문제로 발생할 수 있다.
그래서 다시
// 몬스터 잡으면 -> 아이템이 인벤토리에 들어온다.
// 1. 몬스터를 잡음
// 2. DB 먼저 적용
// 3. 아이템 생성 + 인벤토리에 추가
하지만 DB처리 시간이 오래걸린다는 단점이 있었다.
그래서 시점을 저장했다가, 복원해서 다시 재생시킬 수 있는 기능인 코루틴을 사용
밑에 코드 예제가 있지만, 사실 좀 직접 서브 프로젝트에 사용한다고 하면 복잡하고, 문법이 지저분한 편인 것 같아서 사용하기가 좀 꺼려진다.
C#과 다르게 C++에서 코루틴이란 거 자체는 문법이 굉장히 복잡하지만, 사실상 단순한 문법이 아니라 일종의 프레임워크를 제공해준셈 같다. 사용하기 위해선 틀에 맞춘 함수를 사용해야 기능이 된다.
- Coroutine.cpp
#include <iostream>
using namespace std;
#include <list>
#include <vector>
#include <map>
#include <algorithm>
#include <ranges>
#include <concepts>
#include <coroutine>
#include "MyCoroutine.h"
// 오늘의 주제 : Coroutine
// 유니티 = 코루틴
// - C#에서 있는 코루틴 문법을 유니티에서 적극적으로 채용해서 제공
struct CoroutineObject
{
// 1) promise 객체는 다음과 같은 인터페이스를 제공해야 함 (코드에 따라서 그에 대응하는 프레임워크 함수를 정의해야 사용가능)
// - 기본 생성자 : promise 객체는 기본 생성자로 만들어질 수 있어야 함
// - get_return_object : 코루틴 객체를 반환 (resumable object)
// - return_value(val) : co_return val에 의해 호출됨 (코루틴이 영구적으로 끝나지 않으면 없어도 됨)
// - return_void() : co_return에 의해 호출됨 (코루틴이 영구적으로 끝나지 않으면 없어도 됨)
// - yield_value(val) : co_yield에 의해 호출됨
// - initial_suspend() : 코루틴이 실행 전에 중단/연기될 수 있는지
// - final_suspend() : 코루틴이 종료 전에 중단/연기될 수 있는지
// - unhandled_exception() : 예외 처리시 호출됨
struct promise_type
{
CoroutineObject get_return_object() { return {}; }
std::suspend_never initial_suspend() const noexcept { return {}; }
std::suspend_never final_suspend() const noexcept { return {}; }
void return_void() { }
void unhandled_exception() { }
};
};
CoroutineObject HelloCoroutine()
{
co_return;
}
/*{
Promise prom; (get_return_object)
co_await prom.initial_suspend();
try
{
// co_return, co_yield, co_await를 포함하는 코드
}
catch (...)
{
prom.unhandled_exception();
}
co_await prom.final_suspend();
}*/
Future<int> CreateFuture()
{
co_return 2021;
}
Generator<int> GenNumbers(int start = 0, int delta = 1)
{
int now = start;
while (true)
{
co_yield now; // yield return
now += delta;
}
}
Job PrepareJob()
{
// co_await [Awaitable]
co_await std::suspend_never();
}
int main()
{
// 함수가 코루틴이 되려면...
// - co_return
// - co_yield
// - co_await
// 코루틴을 사용할 수 있는 Framework를 제공한다 (...)
// 3가지 요소로 구성
// - promise 객체
// - 코루틴 핸들 (밖에서 코루틴을 resume / destroy 할 때 사용. 일종의 리모컨)
// - 코루틴 프레임 (promise 객체, 코루틴이 인자 등을 포함하는 heap 할당 객체) (함수를 호출할 때, 스택 프레임이 생성되는 것처럼 코루틴을 실행할 때도 코루틴 프레임이란 게 만들어져서 현재 상태를 보존해야하니까 heap에 할당됨)
// co_yield, co_await, co_return을 함수 안에서 사용하면, 그 함수는 코루틴이 됨.
/*{
Promise prom; (get_return_object)
co_await prom.initial_suspend();
try
{
// co_return, co_yield, co_await를 포함하는 코드
}
catch (...)
{
prom.unhandled_exception();
}
co_await prom.final_suspend();
}*/
// co_await 플로우
/*{
if awaitable.await_ready() returns false;
suspend coroutine
awaitable.await_suspend(handle) returns:
void:
awaitable.await_suspend(handle);
coroutine keeps suspended
return to caller
bool:
bool result = awaitable.await_suspend(handle);
if (result)
coroutine keeps suspended
return to caller
else
return awaitable.await_resume()
another coroutine handle:
anotherCoroutineHandle = awaitable.await_suspend(handle);
anotherCoroutineHandle.resume();
return to caller;
return awaitable.await_resume();
}*/
auto future = CreateFuture();
// TODO : 다른걸 하다...
cout << future.get() << endl;
auto numbers = GenNumbers(0, 1);
for (int i = 0; i < 20; i++)
{
numbers.next();
cout << " " << numbers.get();
}
auto job = PrepareJob();
job.start();
}
- MyCoroutine.h
#pragma once
#include <coroutine>
#include <iostream>
using namespace std;
// 코루틴 객체
template<typename T>
class Future
{
public:
Future(shared_ptr<T> value) : _value(value) { }
T get() { return *_value; }
private:
shared_ptr<T> _value;
public:
struct promise_type
{
Future<T> get_return_object() { return Future<T>(_ptr); }
void return_value(T value) { *_ptr = value; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() { }
// 데이터
shared_ptr<T> _ptr = make_shared<T>();
};
};
template<typename T>
class Generator
{
public:
struct promise_type;
using handle_type = coroutine_handle<promise_type>;
Generator(handle_type handle) : _handle(handle)
{
}
~Generator()
{
if (_handle)
_handle.destroy();
}
T get() { return _handle.promise()._value; }
bool next()
{
_handle.resume(); // 중요!
return !_handle.done();
}
private:
handle_type _handle; // 코루틴 핸들로 코루틴을 재게할지 삭제할지 등 관리해주는 용도
public:
struct promise_type
{
Generator<T> get_return_object() { return Generator(handle_type::from_promise(*this)); }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(const T value) { _value = value; return {}; }
std::suspend_always return_void() { return {}; }
void unhandled_exception() { }
T _value;
};
};
class Job
{
public:
struct promise_type;
using handle_type = coroutine_handle<promise_type>;
Job(handle_type handle) : _handle(handle)
{
}
~Job()
{
if (_handle)
_handle.destroy();
}
void start()
{
if (_handle)
_handle.resume();
}
private:
handle_type _handle;
public:
struct promise_type
{
Job get_return_object() { return Job(handle_type::from_promise(*this)); }
std::suspend_always initial_suspend() { cout << "Prepare Job" << endl; return {}; }
std::suspend_never final_suspend() noexcept { cout << "Do Job" << endl; return {}; }
std::suspend_never return_void() { return {}; }
void unhandled_exception() { }
};
};
'C++20 > 4th Emperor' 카테고리의 다른 글
[C++20 - 4th Emperor] Chapter 03. Range (0) | 2023.07.25 |
---|---|
[C++20 - 4th Emperor] Chapter 02. Module (0) | 2023.07.25 |
[C++20 - 4th Emperor] Chapter 01. Concept (0) | 2023.07.24 |
댓글