언어별 가이드
생성된 코드를 프로젝트에 적용하고 사용하는 방법입니다.
언어마다 준비물, 배치 방법, 주의사항, 트러블슈팅이 다르므로 문서를 나눠 두었습니다.
생성물을 적용하기 전에 빌드 자체가 실패한다면 트러블슈팅을 보세요.
문서 선택
모두 recipe의 Targets에 항목 하나로 적고, Type으로 어느 것인지 정합니다.
| 언어 | Type |
|---|---|
| C# / Unity | csharp |
| TypeScript | typescript |
| C++ | cpp |
| C | c |
| Unreal | unreal |
| Go | go |
| Rust | rust |
| Python | python |
| Java | java |
| Kotlin | kotlin |
| Swift | swift |
| Lua | lua |
| Ruby | ruby |
| PHP | php |
| Dart | dart |
| HTML 문서 | html |
필요한 버전
| 대상 | 요구사항 | 비고 |
|---|---|---|
| C# / Unity | Unity 6 이상 (C# 9 / netstandard2.1) | 설정할 것이 없습니다. 유니티 내장 정의로 스스로 판별하고, UniTask 같은 외부 패키지도 필요 없습니다 |
| TypeScript | 4.5 이상, 컴파일 타겟 ES2020 이상 | 테이블 리더가 BigInt를 사용합니다 |
| C++ | C++17 이상 | |
| C | C99 이상 | 헤더는 C++에서도 include할 수 있습니다 (extern "C") |
| Unreal | 4.x ~ 5.x | UE 4.27.2의 실제 UnrealHeaderTool로 검증합니다 |
| Go | 1.21 이상 | 생성되는 go.mod가 go 1.21을 선언합니다. CI는 1.23으로 검증합니다 |
| Rust | edition 2021 | 생성되는 Cargo.toml이 선언합니다 |
| Python | 3.12로 검증 | 그 아래 버전은 확인하지 않았습니다 |
| Java | 21로 검증 | 테이블 리더에 특별한 문법은 없지만 그 아래는 확인하지 않았습니다 |
| Kotlin | 2.1로 검증 | |
| Swift | 5.9 이상. 6.1과 6.3으로 검증 | 언어 모드 5와 6 둘 다 확인합니다. MAC 검증과 swift-crypto |
| Lua | LuaJIT 2.1 또는 5.3 이상. 5.4로 검증 | 순수 5.1과 5.2는 숫자가 double이라 지원하지 않습니다. 네이티브 모듈 |
| Ruby | 3.2로 검증 | |
| PHP | 8.1 이상 | enum을 사용합니다 |
| Dart | 3.6 이상 | null safety가 필요합니다 |
「검증」은 CI가 매 실행마다 그 버전으로 생성물을 컴파일하거나 실행해서 값을 대조한다는 뜻입니다.
추측이 아닙니다. 표에 없는 하위 버전은 동작할 수도 있지만 확인된 바 없습니다.
공통으로 알아둘 것
추가 의존성 없음
생성된 코드는 그 자체로 완결된 패키지 하나입니다.
프로젝트에 포함시킨다고 해서 새로 설치할 라이브러리나 맞춰야 할 버전이 생기지 않습니다.
기본값에서 그렇다는 뜻이고, 옵션을 켜면 세 자리에서 달라집니다. Rust와 C, C++의 데이터 갱신기, Python의 암호화된 파일 읽기, Swift 의 MAC 검증입니다.
셋 다 「직접 구현이 성능에서 크게 불리한 자리는 이미 있는 것을 쓴다」는 같은 판단입니다. 무엇이 어디에 적히는지는 의존 패키지에 표로 있습니다.
모든 언어에서 바이너리 리더가 출력 폴더에 함께 생성됩니다.
플러그인 설치도, include 경로 설정도 없습니다. 넣으면 그대로 컴파일됩니다.
Go는 go.mod, Rust는 Cargo.toml, 언리얼은 Build.cs까지 함께 나옵니다.
테이블 리더를 lib/에서 가져다 쓰지 않고 매번 새로 쓰는 이유는, 생성된 코드와 다른 시기의
테이블 리더가 짝지어지는 일을 불가능하게 만들기 위해서입니다.
원본은 임베디드 리소스 하나이므로 lib/와 어긋날 수도 없습니다.
타입당 파일 하나
테이블, enum, 상수 세트마다 파일이 하나씩 생성됩니다.
시트에서 테이블을 지우면 그 파일도 사라집니다. 스윕이 이번 실행에서 쓰지 않은 생성 파일을 지웁니다.
지워지는 것은 「쓰지 않은 전부」가 아니라, 헤더에 Generated by Tabbit이 적힌 파일 중 이번에
쓰지 않은 것입니다.
그 마커가 허가증이고 그것을 쓰는 것은 이 도구뿐이므로, 남의 소스가 든 폴더를 가리켜도 안전합니다.
생성물을 손으로 고쳐 쓴다면 recipe 항목에 "Sweep": false를 넣으세요.
생성 파일을 편집하는 것은 recipe에 한 줄 적을 만한 결정입니다.
데이터만 나가도 되는 변경과 코드가 함께 나가야 하는 변경
시트를 고쳤을 때 데이터 파일만 새로 올리면 되는지, 코드까지 다시 배포해야 하는지는 무엇을 고쳤느냐가 정합니다.
| 바꾼 것 | 다시 나가야 하는 것 |
|---|---|
| 테이블의 행 값 | 데이터만 |
| 테이블의 컬럼 — 추가, 삭제, 이름 변경, 순서 변경 | 데이터만 (조건은 아래) |
| 테이블 컬럼의 타입, 고정배열 개수 | 데이터 + 코드 |
| enum — 레이블을 끝에 추가 | 코드. 그 값을 쓰는 행을 내보낸다면 데이터도 함께 |
| enum — 기존 레이블의 값이 밀리는 변경 | 데이터 + 코드. 이미 내보낸 데이터의 숫자가 다른 레이블을 가리키게 됩니다 |
| 상수 세트 — 상수 추가, 삭제, 값 변경 | 코드만. 데이터 파일에는 아무것도 나가지 않습니다 |
enum과 상수 세트는 데이터가 아니라 코드에 적힙니다.
상수 세트는 생성된 소스의 상수 선언이 전부입니다. 값을 하나 고쳐도 그 값이 실린 데이터 파일이 없으므로, 코드를 다시 생성해 빌드하기 전에는 아무것도 달라지지 않습니다.
enum도 레이블의 이름과 값은 코드에 있고 데이터에는 숫자만 실립니다. 그래서 레이블을 새로 추가하고 그 값을 쓰는 행을 내보내면, 이미 배포된 빌드에는 자기가 이름을 모르는 값이 도착합니다.
정리하면 enum과 상수 세트를 건드리는 변경은 데이터만 패치하는 흐름으로 끝나지 않습니다. 코드 배포가 함께 있어야 합니다.
상수는 특히 조심해야 합니다.
다른 변경은 적어도 데이터 파일이 바뀌므로 무언가 나갔다는 흔적이 남습니다. 상수는 그 흔적이 하나도 없습니다. 빌드는 성공하고, 출력도 정상이고, 매니페스트 해시까지 그대로라 업데이터가 받을 파일도 없습니다.
값을 고쳐 놓고 배포했다고 넘어가기 가장 쉬운 경우이면서, 잘못됐다는 신호가 어디에도 없습니다. 상수를 고쳤다면 코드 재생성과 빌드 배포까지가 그 변경의 일부입니다.
상수 세트와 enum이 갈리는 지점이 하나 있습니다.
상수는 데이터에 흔적이 없으니 코드만 나가면 끝입니다. 반면 enum은 그 값이 이미 데이터에 실려 나가 있습니다.
그래서 기존 레이블의 값이 밀리는 변경은 반대 방향의 의무가 생깁니다. 코드뿐 아니라 이미 내보낸 데이터도 전부 다시 내보내야 합니다. 아래 2번이 그 이야기입니다.
테이블 컬럼은 반대입니다.
컬럼은 이름이 아니라 태그로 파일에 실리 므로 추가, 삭제, 이름 변경, 순서 변경은 이미 배포된 빌드가 그대로 읽습니다. 데이터만 올려도 됩니다.
조건은 시트에 태그(@N)를 달아 두는 것과, 지운 컬럼에 tombstone(#이름@N)을 남기는 것입니다.
어느 변경이 견디고 어느 변경이 코드와 함께 나가야 하는지는 바이너리 형식 — 라이브 서비스 시나리오에 표로 있습니다.
로드 후 참조의 자동 연결
foreign 필드는 파일에 인덱스로 저장되고, readAll이 모든 테이블을 읽은 뒤 실제 레코드
참조로 바꿔줍니다.
테이블 하나만 따로 읽으면 그 단계가 없으므로 참조는 비어 있습니다.
Rust만 예외로 참조를 인덱스 그대로 둡니다.
레코드가 서로를 참조하면 그래프가 되는데 Rust는 그런 소유 구조를 허용하지 않기 때문입니다.
find로 직접 찾아 사용하면 됩니다.
데이터 파일 확장자의 인자 지정
기본값은 recipe의 BinaryTableFileExtension이 그대로 들어가므로 보 통은 아무것도 넘기지 않으면
됩니다.
넘겨야 하는 경우는 패키징 과정에서 파일 이름이 바뀔 때입니다.
유니티는 확장자가 .bytes인 것만 TextAsset으로 포함하고, 엔진마다 이런 제약이 다릅니다.
기본 인자가 있는 언어는 두 번째 인자로 받습니다. C#, C++, TypeScript, Python, Ruby, PHP, Dart, Kotlin, 언리얼입니다.
없는 언어는 이름이 다른 짝을 하나 더 생성합니다.
| 언어 | 이름 |
|---|---|
| Java | 오버로드 |
| Go | ReadAllWithExtension |
| Rust | read_all_with_extension |
| C | <Accessor>_LoadAllWithExtension |