TL;DR — 새로운 계층마다 전용 결과 타입을 정의하던 경험을 돌아보며, 함수 호출의 공통 결과는
TkResult, 상세 원인은TkDiagnostic으로 분리했습니다. Vulkan의VkResult를 참고해 공통 계층에 결과 반환과 세부 진단에 대한 작은 계약을 두고, 다양한 곳에서 같은 양식으로 성공/실패를 표현할 수 있게 구성하였습니다.
Table of contents
Open Table of contents
들어가며
PrivateServer에는 이미 공통 결과 타입인 NrStatus와 NrResult<T>가 있었습니다. 하지만 새로운 계층을 추가할 때마다 그 계층의 상태와 실패를 표현할 enum, 성공 데이터를 전달할 struct, 하위 실패를 다시 변환할 규칙을 자주 새로 정의했습니다.
결과 타입은 필요에 의해 만들어졌기 때문에 불필요하다고만 볼 수는 없습니다. 다만 비슷한 패턴을 가졌지만, 레이어가 추가될 때마다 매번 비슷한 용도의 비슷한 코드를 작성하고 확인하는 것은 줄일 수 있다고 생각했습니다.
이 글에서는 Vulkan의 결과 반환 양식을 참고해 TkResult와 TkDiagnostic으로 책임을 나눈 과정, 공통 계층이 소유하는 계약, 그리고 Packet Tool에서 실제로 적용되는 방식에 대해 순차적으로 정리해 나갑니다.
이 글에서 다루는 내용:
- 공통 결과 타입이 있어도 계층별 결과 타입이 반복된 이유
- Vulkan의
VkResult에서 참고한 양식 TkResult와TkDiagnostic의 책임 분리- 공통 계층, Tool, consumer 사이의 callback 계약
- Packet Tool에 적용하며 확인한 일관성과 한계
사전 지식: C/C++ 함수 반환값, callback function pointer
1. 공통 결과 타입만으론 해결할 수 없는 문제
1.1 NrStatus와 NrResult<T>가 제공한 공통 기반
PrivateServer의 NrStatus는 공통 오류 코드와 native 오류 코드를 함께 보관합니다. NrResult<T>는 이 상태와 성공 값을 묶어 반환합니다.
// 설명에 필요한 public member만 발췌했습니다.
class NrStatus
{
public:
[[nodiscard]] constexpr static NrStatus Success() noexcept;
[[nodiscard]] constexpr static NrStatus Failure(
NrErrorCode errorCode,
NrNativeErrorCode nativeErrorCode = 0) noexcept;
[[nodiscard]] constexpr bool Succeeded() const noexcept;
[[nodiscard]] constexpr bool Failed() const noexcept;
private:
NrErrorCode errorCode_ = NrErrorCode::Success;
NrNativeErrorCode nativeErrorCode_ = 0;
};NrStatus.h
// 설명에 필요한 public member만 발췌했습니다.
template <typename T>
class NrResult
{
public:
explicit NrResult(T value);
[[nodiscard]] static NrResult Failure(NrStatus status) noexcept;
[[nodiscard]] const NrStatus& Status() const noexcept;
[[nodiscard]] bool Succeeded() const noexcept;
[[nodiscard]] T TakeValue();
private:
NrStatus status_;
std::optional<T> value_;
};NrResult.h
이 구조는 네트워크 계층에서 공통 오류와 성공 값을 전달하는 기준을 제공했고, 값과 오류를 하나의 객체로 다룬다는 점에서 어느 정도의 편리함도 존재했습니다.
1.2 새로운 계층에서 반복된 선택
이어서 World 계층에서도 WorldResult<T, TError> 같은 공통 template을 이용해 World 내부 로직 처리의 결과를 처리하게 만들었습니다. 그런데 다음과 같이 World의 내부 레이어가 반환하는 의미를 담기 위한 전용 enum도 계속 늘어나기 시작했습니다.
enum class WorldSpatialIndexBuildResult : std::uint8_t
{
Built = 0,
InvalidInput,
AllocationFailed,
};WorldSpatialIndex.h
enum class WorldFixedStepTakeResult : std::uint8_t
{
Taken = 0,
NotDue,
InvalidArgument,
InvalidState,
ServerTickOverflow,
};WorldFixedStepSchedule.h
각 결과를 보면 NotDue는 WorldFixedStep 레이어에서 담당하는 도메인 로직의 결과이고, InvalidArgument, AllocationFailed는 여러 계층에서 반복해 사용할 수 있는 일반적인 결과에 가깝다는 것을 확인할 수 있습니다.
그래서 여러 종류의 enum에 들어간 결과를 매번 코드에서 서로 다른 result를 만들고, 이를 확인하면서 정상적으로 처리가 진행됐는지, 레이어 내부 도메인 로직 처리 결과를 확인하는 등의 불필요한 작업이 반복적으로 진행됐습니다.
- 정상적인 성공
- 아직 처리할 필요가 없는 도메인 상태
- 잘못된 호출
- 입력 데이터 오류
- 메모리나 I/O와 같은 실행 실패
성공 시 여러 값을 전달해야 하면 NrResult<T> 또는 WorldResult<T>의 T로 사용할 struct도 추가했습니다. 새로운 계층마다 결과 enum, 성공 payload와 변환 코드를 설계하는 작업이 계속 반복됐습니다.
2. Vulkan에서 결과를 처리하는 방법
앞에서 확인한 문제를 많은 사람들이 사용하는 프로젝트에서는 어떤 방식으로 해결하는지 확인해보기 위해 Vulkan을 레퍼런스 삼아 조사를 해보았습니다.
2.1 VkResult는 단순한 성공과 실패가 아니다
Vulkan API는 여러 command의 결과를 공통 VkResult로 표현합니다. VK_SUCCESS만 있는 단순한 boolean 결과가 아니라, 호출자가 다음 동작을 결정할 수 있는 성공 상태와 오류 범주를 함께 정의합니다.
| 분류 | 예시 | 호출자가 판단할 내용 |
|---|---|---|
| 성공 | VK_SUCCESS | 작업이 완료됐는가 |
| 진행 상태 | VK_NOT_READY, VK_TIMEOUT | 기다리거나 다시 시도해야 하는가 |
| 부분 성공 | VK_INCOMPLETE | 더 큰 출력 공간으로 다시 호출해야 하는가 |
| 실패 | VK_ERROR_OUT_OF_HOST_MEMORY | 작업을 중단하거나 상위로 실패를 전달해야 하는가 |
반면 참과 거짓 자체가 결과인 Vulkan Loader 내부의 predicate는 bool을 사용합니다. 예를 들어 문자열 목록에 특정 값이 있는지를 검사하는 함수는 새로운 result enum을 만들지 않고 bool을 반환합니다.
이 구조에서 핵심은 모든 함수의 반환 타입을 하나로 묶는 것이 아니었습니다. 호출자의 제어 흐름을 바꾸는 공통 결과는 공유하고, predicate나 도메인 처리 결과는 그 의미에 맞는 타입을 사용하면 됩니다. 참과 거짓 자체가 결과라면 새로운 구조체 대신 bool을 사용할 수 있습니다.
따라서 public API와 내부 함수의 구분보다 반환값의 의미가 기준이 됩니다. 성공과 실패를 상위 호출자가 해석해야 하면 공통 결과 타입을 사용하고, 정상적인 도메인 상태를 구분해야 하면 전용 타입을 사용합니다.
2.2 결과 범주와 상세 설명은 다른 정보다
그러나 반환 결과만으로는 세부적인 내용을 같이 전달할 수 없습니다. 그래서 Khronos Vulkan Loader의 내부 함수들은 VkResult를 반환하고 하위 결과를 전파합니다. 구체적인 원인은 별도의 logging 경로로 남깁니다.
PrivateServerToolKit에서는 Vulkan에서 상세 설명을 별도 logging으로 처리하는 점을 차용하여 다음과 같이 두 가지 정보를 API 호출 경로로 전달하기로 했습니다.
함수 호출 결과
├─ 공통 결과 범주(TkResult)
│ └─ 호출자의 제어 흐름을 결정
│
└─ 상세 진단(TkDiagnostic)
└─ 사람과 도구가 실패 원인을 해석
공통 반환 타입은 TkResult, 상세 진단의 공통 형식은 TkDiagnostic으로 정했습니다.
3. TkResult와 TkDiagnostic
3.1 TkResult는 제어 흐름을 위한 공통 enum
TkResult에는 여러 Tool과 consumer가 공통으로 해석할 수 있는 결과만 둡니다.
typedef enum TkResult
{
TK_SUCCESS = 0,
TK_ERROR_UNKNOWN = -1,
TK_ERROR_INVALID_ARGUMENT = -2,
TK_ERROR_BUFFER_TOO_SMALL = -3,
TK_ERROR_OUT_OF_MEMORY = -4,
TK_ERROR_INVALID_DATA = -5,
TK_ERROR_IO = -6,
TK_RESULT_MAX_ENUM = 0x7FFFFFFF
} TkResult;TkResult.h
이 값은 어떤 작업이 정확히 왜 실패했는지 모두 설명하지 않습니다. 호출자가 실패 범주를 보고 반환, 재시도, buffer 확장 또는 입력 거부 같은 제어 흐름을 선택할 수 있을 정도로만 구분합니다.
| 상황 | TkResult |
|---|---|
| null output pointer와 같은 잘못된 호출 | TK_ERROR_INVALID_ARGUMENT |
| caller가 제공한 output buffer의 용량 부족 | TK_ERROR_BUFFER_TOO_SMALL |
| 입력 데이터 해석 실패 | TK_ERROR_INVALID_DATA |
| 파일이나 OS resource의 I/O 실패 | TK_ERROR_IO |
NotDue, NeedMoreData, Accepted처럼 호출자가 구분해야 하는 도메인 결과는 TkResult에 추가하지 않고 해당 도메인에 맞는 타입을 사용합니다. 공통 실패와 도메인 결과를 나누는 것이지, 모든 함수의 반환 타입을 하나로 만드는 것은 아닙니다.
3.2 TkDiagnostic은 상세 원인을 전달
TkResult는 다소 범용적인 결과를 담아냅니다. 그래서 상세 원인은 공통 진단 타입인 TkDiagnostic으로 분리했습니다.
typedef struct TkDiagnostic
{
TkDiagnosticSeverity severity;
const char *id;
const char *message;
TkDiagnosticLocation location;
} TkDiagnostic;
typedef void (*TkDiagnosticCallback)(
const TkDiagnostic *diagnostic,
void *userData);
typedef struct TkDiagnosticCallbackInfo
{
TkDiagnosticCallback callback;
void *userData;
} TkDiagnosticCallbackInfo;TkDiagnostic.h
TkResult와 TkDiagnostic은 같은 실패를 서로 다른 관점에서 전달합니다.
| 정보 | 역할 |
|---|---|
TkResult | 호출자가 해석하는 공통 결과 범주 |
severity | 정보, 경고와 오류의 심각도 |
id | Tool이 정의하는 안정적인 원인 식별자 |
message | 사람이 읽을 수 있는 상세 설명 |
location | source 이름과 선택적인 위치 정보 |
예를 들어 여러 입력 오류가 모두 TK_ERROR_INVALID_DATA를 반환하더라도 Tool은 각각 다른 id, message와 location을 전달할 수 있습니다. 호출자는 TkResult로 실행 흐름을 결정하고, consumer는 diagnostic으로 구체적인 원인을 표시하거나 보관합니다.
4. 공통 계층은 작은 계약만 소유한다
공통 계층은 여러 Tool이 공유하는 기초 타입과 작은 연산을 제공하는 header-only INTERFACE target입니다. 개별 Tool의 도메인 의미나 실행 정책은 소유하지 않습니다.
callback 실행에는 다음 작은 helper를 사용합니다.
static inline void TkEmitDiagnostic(
TkDiagnosticCallbackInfo callbackInfo,
const TkDiagnostic *diagnostic)
{
if (callbackInfo.callback == NULL)
{
return;
}
callbackInfo.callback(diagnostic, callbackInfo.userData);
}TkDiagnostic.h
callback == NULL은 diagnostic을 버리는 유효한 설정입니다. callback이 있으면 Tool API를 호출한 thread에서 API가 반환되기 전에 동기적으로 실행합니다.TkDiagnostic과 문자열은 callback 동안만 유효한 borrowed data입니다. consumer가 API 반환 이후에도 정보를 사용하려면 필요한 값을 자신의 저장소로 복사해야 합니다.
공통 계층에 logger나 diagnostic vector를 두지 않은 이유도 여기에 있습니다. 출력, 보관과 동기화 정책은 consumer마다 다르기 때문에 공통 계층이 하나의 정책을 강제하지 않습니다.
5. Tool에서 consumer까지 이어지는 callback 파이프라인
5.1 같은 인터페이스로 서로 다른 정보를 전달한다
consumer는 callback과 userData를 TkDiagnosticCallbackInfo에 담아 Tool API에 전달합니다. Tool은 실행 결과를 TkResult로 반환하고, 상세 설명이 필요한 경우 TkDiagnostic을 구성해 공통 계층의 TkEmitDiagnostic으로 전달합니다.
flowchart LR
Consumer["Consumer
callback + userData"]
Tool["Tool API"]
Result["TkResult
제어 흐름"]
SharedLayer["공통 계층
TkEmitDiagnostic"]
Callback["Consumer callback
출력 / 복사 / 변환"]
Consumer -->|callback info| Tool
Tool -->|return| Result
Result --> Consumer
Tool -->|TkDiagnostic| SharedLayer
SharedLayer --> Callback
공통 계층은 diagnostic의 내용을 해석하지 않습니다. 각 Tool은 같은 구조 안에 자신이 알고 있는 정보를 채웁니다.
| Tool이 알고 있는 정보 | Diagnostic 구성 예시 |
|---|---|
| source 기반 입력 오류 | 안정적인 id, 입력 파일 이름과 위치 |
| 지원하지 않는 값 | 기대한 값과 실제 값을 설명하는 message |
| I/O 작업 실패 | 공통 I/O 결과와 실패한 작업을 나타내는 id |
| source가 없는 실행 오류 | 위치 없는 diagnostic과 상세 message |
Tool마다 전달하는 정보는 달라도 consumer가 받는 형식은 TkDiagnostic으로 유지됩니다. 새로운 Tool을 추가할 때 별도 callback 형식을 만들지 않고 id, message와 location의 의미만 Tool의 도메인에 맞게 정할 수 있습니다.
5.2 consumer가 callback 동작을 선택한다
consumer는 Tool 코드를 변경하지 않고 callback만 바꿔 diagnostic 처리 방식을 선택할 수 있습니다.
| Consumer 정책 | Callback 동작 |
|---|---|
| Diagnostic을 사용하지 않음 | callback == nullptr로 전달 |
| Console 출력 | severity, id와 message를 원하는 형식으로 출력 |
| Build 결과 수집 | borrowed data를 consumer 저장소로 복사 |
| Editor 연동 | location을 파일과 위치 표시로 변환 |
Diagnostic을 사용하지 않는 consumer는 disabled callback을 전달할 수 있습니다.
const TkDiagnosticCallbackInfo disabled = {nullptr, nullptr};
Diagnostic을 보관하려는 consumer는 callback 안에서 값을 복사합니다.
struct StoredDiagnostic
{
std::string id;
std::string message;
std::string sourceName;
};
void StoreDiagnostic(
const TkDiagnostic* diagnostic,
void* userData)
{
auto& stored = *static_cast<StoredDiagnostic*>(userData);
stored.id = diagnostic->id;
stored.message = diagnostic->message;
stored.sourceName = diagnostic->location.sourceName != nullptr
? diagnostic->location.sourceName
: "";
}
StoredDiagnostic stored;
TkDiagnosticCallbackInfo callbackInfo = {
StoreDiagnostic,
&stored,
};consumer.cpp
userData는 callback 함수와 consumer의 상태를 연결합니다. Diagnostic은 callback 동안만 유효하므로 위 예시처럼 문자열을 복사하면 Tool API 반환 이후에도 정보를 사용할 수 있습니다.
이 구조에서는 consumer가 바뀌어도 Tool의 반환 타입과 diagnostic 생성 코드는 그대로 유지됩니다. 반대로 새로운 Tool을 추가해도 공통 callback 형식을 재정의할 필요가 없습니다.
5.3 공통 계층, Tool과 consumer의 책임
| 주체 | 결과 책임 | Diagnostic 책임 |
|---|---|---|
| 공통 계층 | 공통 TkResult 값 정의 | 공통 POD, callback 형식과 동기 emit helper 제공 |
| Tool | 상황에 맞는 TkResult 선택 | 안정적인 ID, message, location과 emit 시점 결정 |
| Consumer | 결과에 따라 반환·재시도·중단 | 출력, 복사, 필터링, 저장과 동기화 정책 결정 |
이 분리를 통해 공통 계층은 커지지 않으면서도 Tool과 consumer가 같은 언어로 통신할 수 있습니다.
6. diagnostic의 라이프사이클 관리
callback 기반 diagnostic은 consumer에게 선택권을 주지만 안정적으로 사용하기 위해 몇 가지 규칙이 필요합니다.
- borrowed diagnostic을 보관하려면 callback 안에서 복사해야 합니다.
- 여러 thread에서 같은
userData를 사용하려면 thread-safe하게 만들어야 합니다. TkResult만 기록하면 상세 원인을 파악할 수 없습니다.- Tool은 diagnostic ID의 의미를 공개한 뒤 임의로 바꾸지 않아야 합니다.
- 공통 계층에 Tool별 편의 기능을 계속 추가하면 다시 범용 utility 계층이 될 수 있습니다.
그래서 공통 계층은 공통 형식과 최소 helper만 소유합니다. Tool과 consumer의 정책을 공통 계층으로 끌어올리지 않는 것이 현재 구조를 유지하는 조건입니다.
7. 적용 예시: Packet Tool
앞에서 정리한 TkResult, TkDiagnostic, callback과 공통 계층의 책임은 Packet Tool의 build-time compiler와 generated codec에 함께 적용됩니다.
7.1 하나의 결과 어휘를 전파한다
Packet Tool의 C++ 생성 흐름은 다음 단계로 구성됩니다.
schema file 읽기
-> JSON parsing
-> semantic validation
-> PacketDescriptor 구성
-> C++ header 생성
-> generated file commit
각 함수는 별도의 parser result나 generator result를 만들지 않고 TkResult를 반환합니다. 상위 단계는 하위 TkResult를 그대로 확인하고 전파합니다.
PacketDescriptorSet descriptorSet;
PacketSchemaCompiler schemaCompiler(compileInfo.diagnosticCallback);
TkResult result = schemaCompiler.Compile(
compileInfo.inputPaths,
compileInfo.inputPathCount,
&descriptorSet);
if (result != TK_SUCCESS)
{
return result;
}TkPacketCppCompiler.cpp
Parser, descriptor builder와 file committer처럼 상세 원인을 알고 있는 단계는 같은 callback 형식으로 diagnostic을 전달합니다. C++ generator는 현재 TkResult만 반환하지만, 이를 위해 별도의 generator result 타입을 추가하지 않습니다.
7.2 같은 callback으로 서로 다른 원인을 전달한다
Packet Tool은 하나의 TkResult에 여러 diagnostic을 대응시킵니다.
| 상황 | TkResult | Diagnostic ID |
|---|---|---|
| 중복된 field 이름 | TK_ERROR_INVALID_DATA | PSTK-PACKET-DUPLICATE-FIELD-NAME |
| 지원하지 않는 schema version | TK_ERROR_INVALID_DATA | PSTK-PACKET-UNSUPPORTED-SCHEMA-VERSION |
| 알 수 없는 field type | TK_ERROR_INVALID_DATA | PSTK-PACKET-UNKNOWN-FIELD-TYPE |
| 출력 파일 기록 실패 | TK_ERROR_IO | PSTK-PACKET-OUTPUT-WRITE-FAILED |
| encode buffer 부족 | TK_ERROR_BUFFER_TOO_SMALL | PSTK-PACKET-INVALID-PAYLOAD-SIZE |
각 단계는 실패 범주와 함께 자신이 알고 있는 source 이름, logical path와 설명을 TkDiagnostic으로 구성합니다.
TkResult ReportFailure(
const TkDiagnosticCallbackInfo& callbackInfo,
const TkResult result,
const char* const id,
const std::string& sourceName,
const std::string& logicalPath,
const std::string& detail)
{
std::string message = sourceName;
if (!logicalPath.empty())
{
message += ": ";
message += logicalPath;
}
message += ": ";
message += detail;
EmitDiagnostic(callbackInfo, id, message, sourceName);
return result;
}TkPacketDiagnostic.cpp
consumer는 5절에서 만든 callback info를 Packet Tool의 public API에 그대로 전달할 수 있습니다.
TkPacketCppCompileInfo compileInfo = {
inputPaths,
inputPathCount,
outputDirectory,
namespaceName,
callbackInfo,
};
const TkResult result = TkPacketCompileCpp(&compileInfo);
if (result != TK_SUCCESS)
{
// result로 제어 흐름을 결정하고,
// callback이 복사한 diagnostic으로 상세 원인을 표시합니다.
}consumer.cpp
Generated codec도 TkResult와 callback 계약을 사용합니다. 따라서 build-time compiler뿐 아니라 생성된 consumer-facing 코드까지 같은 결과 파이프라인이 이어집니다.
const TkResult encodeResult = packet.Encode(output, callbackInfo);
const TkResult decodeResult = packet.Decode(input, callbackInfo);
7.3 Validation 이후의 상태 반영
공통 결과를 반환하더라도 실패 과정에서 output이 일부 변경된다면 caller는 별도의 정리 코드를 작성해야 합니다. Generated codec은 validation과 임시 객체 구성을 모두 마친 뒤 마지막에 상태를 반영합니다.
다음 코드는 generated Decode에서 field별 읽기와 namespace를 생략하고 상태 반영 순서만 정리한 형태입니다.
TkResult Decode(
TkByteView input,
TkDiagnosticCallbackInfo diagnosticCallback = {})
{
const TkResult validationResult =
ValidateDecodeView(input, PayloadBytes, diagnosticCallback);
if (validationResult != TK_SUCCESS)
{
return validationResult;
}
const std::uint16_t payloadVersion =
ReadLittleEndian<std::uint16_t>(input.data);
const TkResult versionResult = ValidatePayloadVersion(
payloadVersion,
PayloadVersion,
diagnosticCallback);
if (versionResult != TK_SUCCESS)
{
return versionResult;
}
GeneratedPacket decoded{};
// input의 field를 decoded에 기록합니다.
*this = decoded;
return TK_SUCCESS;
}
Decode는 크기와 payload version을 먼저 검증하고 임시 decoded 객체에 field를 기록합니다. 실제 객체를 바꾸는 *this = decoded는 모든 단계가 성공한 뒤에만 실행됩니다. 따라서 TK_SUCCESS가 아니면 기존 상태가 유지되고 caller는 부분적으로 변경된 상태를 따로 정리하지 않아도 됩니다.
Encode도 caller의 output buffer를 먼저 검증하고 local byte array에 전체 payload를 만든 뒤 마지막에 output으로 복사합니다. 이 규칙을 통해 TkResult 반환과 output 보존이 하나의 호출 계약으로 이어집니다.
정리하며
PrivateServer에는 이미 NrStatus, NrResult<T>와 WorldResult<T, TError>가 있었습니다. 그러나 계층이 늘어날 때 공통 실패, 도메인 상태와 상세 정보를 어떤 타입으로 나눌지에 대한 판단은 계속 반복됐습니다.
PrivateServerToolKit에서는 Vulkan의 공통 VkResult와 predicate 구분을 참고해 함수 호출 결과를 TkResult로 통일했습니다. 단순한 성공과 실패만 표현하는 것이 아니라 InvalidArgument, InvalidData, BufferTooSmall, OutOfMemory, IO처럼 호출자가 대응할 수 있는 공통 범주를 제공합니다. 호출자는 이 값을 통해 실행 흐름을 제어합니다.
구체적인 실패 원인은 TkDiagnostic으로 분리했습니다. Tool은 같은 callback 인터페이스 위에서 자신의 ID, message와 location을 구성하고, consumer는 출력, 수집, 변환 또는 무시 정책을 선택합니다. Generated codec까지 이 계약을 사용하면서 Packet Tool의 build-time 단계와 consumer-facing 코드가 같은 결과 파이프라인을 공유하게 됐습니다.
핵심 요약:
- 전용 결과 타입 자체가 아니라 공통 실패를 반복해서 재정의하는 비용을 줄이는 것이 목표입니다.
TkResult는 호출자의 제어 흐름을 위한 공통 결과 범주입니다.TkDiagnostic은 안정적인 ID, message와 source location을 전달합니다.- 공통 계층은 형식과 callback 호출 규칙만 소유합니다.
- Tool은 상세 정보를 구성하고 consumer는 처리 정책을 선택합니다.
NotDue와NeedMoreData같은 도메인 결과는 별도 타입으로 남길 수 있습니다.
참고 자료
- Vulkan
VkResultReference — Vulkan의 공통 성공 상태와 오류 코드를 확인합니다. - Khronos Vulkan Loader
loader.c— 내부 함수의VkResult전파와boolpredicate 사용을 확인합니다. - PrivateServerToolKit Issue #1 — 공통 계약과 Packet compiler 작업의 범위를 확인합니다.
- PrivateServerToolKit
TkResult.h— 프로젝트 공통 결과 값을 확인합니다. - PrivateServerToolKit
TkDiagnostic.h— Diagnostic과 callback 계약을 확인합니다.
이 게시물은 학습한 내용을 바탕으로 초안을 작성한 뒤, LLM의 도움을 받아 내용을 검수하고 다듬어 완성되었습니다.