레코드 안의 참조
상태: 됨 — 모든 언어, 형식 무변경
레코드 그룹의 멤버가 다른 테이블을 가리키면 변환이 멈췄습니다. 표현이 없어서가 아니라 해석과 생성이 그 자리까지 가지 않아서였습니다.
구현 결과는 8절에 있습니다. 설계에서 달라진 것은 두 가지입니다 — 레코드 그룹의 형태가 하나가 아니라 셋이었고, 그 셋을 요구하는 픽스처가 이 개정과 무관한 결함 넷을 함께 드러냈습니다.
1. 문제 — 「아직」이라고 적힌 거부
Record group `GearSlot.MaterialItem` member `Id` references another table. A reference
inside a record group is not supported yet. Move the column out of the group, or carry
the key as a plain `int` for now.
코드의 주석이 무엇이 없는지까지 적어 두었습니다 — 「해석이 필드마다 저장된 키 배열과
세터를 내는데, [j].Member까지 닿게 하는 것을 하지 않았다」. 규칙이 아니라 미룬
자리이고, 그래서 메시지가 「not supported yet」입니다.
이것은 throw입니다. 진단이 아니라 예외라 그 워크북의 변환이 통째로 멈춥니다.
2. 막혀 있는 것의 크기
값이 어느 테이블의 행인지 시트가 적어 두는 선언을 전수 조사한 결과입니다.
| 경우 | 선언 |
|---|---|
| 참조로 승격됨 | 624 |
| 레코드 멤버, 대상은 전부 있음 | 507 |
| 대상이 그 조사에 없음 | 294 |
| 합계 | 1,425 |
507개가 대상 테이블까지 전부 갖춰 놓고 이 거부 하나 때문에 서 있습니다. 테이블 67개에
걸쳐 있고, 많은 곳은 AutoMateEquip 99개 · BattleMapObject 34개 · ShipTemplate 26개입니다.
| 507개의 대상 개수 | 선언 |
|---|---|
| 하나 — 이 문서가 여는 것 | 452 |
| 둘 — 참조가 아니라 검사가 됩니다. 참조가 내는 이름 §6 | 55 |
452개가 이 개정으로 곧바로 레코드 프로퍼티를 얻습니다.
우선순위가 실측으로 뒤집힌 자리입니다. 남은 일을 「다중 대상 58건의 대상별 프로퍼티」로 적어 두었는 데, 그 58건에서 레코드 멤버를 빼면 3건입니다. 나머지는 전부 이 문서의 것이 었습니다.
3. 결정 — 스칼라 참조와 같은 형태로
레코드의 멤버도 참조가 됩니다. 지금 스칼라·배열 참조가 얻는 것을 원소 타입 안에서 똑같이 얻습니다.
| 스칼라 참조가 내는 것 | 레코드 멤버가 낼 것 |
|---|---|
| 저장된 키 멤버 | 원소 타입 안의 저장된 키 멤버 |
| 해석된 레코드 멤버 | 원소 타입 안의 해석된 레코드 멤버 |
| 「가리키는 것이 있는가」 플래그 | 원소 타입 안의 같은 플래그 |
| 로드 뒤 한 번 도는 링킹 | 원소를 돌면서 같은 링킹 |
셋째 줄은 이후 없어졌습니다. 플래그가 키 검사와 같은 값이어서 C#·TypeScript에서도 제거했습니다 — 참조의 해석 여부.
새 형태를 만들지 않는 것이 요지입니다. 소비하는 쪽이 row.Slot[j].Id에서 레코드를 받는
것은 row.ItemId에서 받는 것과 같은 일이어야 하고, 그래야 배울 것이 늘지 않습니다.
4. 형식의 무변경
와이어는 이미 준비되어 있습니다. 파일은 레코드를 멤버마다 한 컬럼으로 싣고(구조체의
배열이 아니라 배열의 구조체), WireColumn이 멤버 컬럼에 IsRef를 이미 채웁니다. 참조 컬럼이
대상의 키 타입으로 실리는 것도 그 경로를 그대로 탑니다.
| 무엇 | 바뀌는가 |
|---|---|
| 형식 버전 | 아니오 |
| 컬럼 element | 아니오 — 참조 멤버는 이미 키의 element로 실립니다 |
| 읽는 바이트 | 아니오 |
| 생성 코드 | 예 — 3절 |
멤버가 배열인 레코드·배열의 배열·다중 중첩이 전부 형식 무변경으로 끝난 것과 같은 이유입니다 — 그 형태들이 이미 실려 있던 것이었듯, 참조 멤버도 이미 실릴 수 있는 컬럼입니다.
그러므로 골든의 바이트는 안 움직여야 합니다. 움직이면 그것은 이 개정이 검출한 것이 아니라 부작용입니다.
5. 담지 않는 것
- 레코드 안의 다중 대상 참조. 이 문서가 여는 것은 대 상이 하나인 멤버 452개이고, 대상이 여럿인 55개는 대상별 프로퍼티가 정해지고 나서입니다. 둘을 합쳐야 507개가 됩니다.
- 점 표기(
Table.Field) 멤버. 멤버가 대상의 필드를 가리키는 형태는 이 개정에서 받지 않습니다. 전체 행 참조부터 열고, 요구가 확인되면 그때 봅니다. _BC변종. 그 문서의 4절.
6. 검증 게이트
| 게이트 | 확인하는 것 |
|---|---|
| 스칼라·배열 참조 무변경 | 지금 있는 참조 테스트 전부. 한 글자도 달라지지 않아야 합니다 |
| 레코드 멤버 참조 | 원소 타입이 키·레코드를 갖고, 링킹이 원소를 도는지 |
| 레코드 배열의 멤버 참조 | 원소가 여럿일 때 각 원소가 제 대상을 무는지 |
| 자르는 레코드 배열 | 길이가 로우마다 다를 때 링킹이 그 길이만 도는지 |
| 모든 언어 컴파일·되읽기 | 원소 타입 안의 멤버가 늘어나므로 언어마다 확인합니다 |
| 골든 | 한 바이트도 바뀌지 않아야 합니다 — 4절 |
| 샘플 재생성 | 507개가 실제로 승격되는지, 그리고 무엇이 걸리는지 |
다섯 번째 줄이 비용입니다. 코어 변경은 작고 확인이 13번입니다 — 참조 키 타입이 같은 형태였고, 거기서 컴파일과 되읽기가 각각 버그를 검출했습니다.
7. 구현 순서
- 거부 삭제 —
Table.cs의member.IsRefthrow. 이것만 지우면 그 뒤가 전부 드러납니다. - 모델 — 레코드 멤버의 참조가 해석되게. 지금 해석은
table.Fields를 돌므로 멤버 필드도 이미 지나갑니다 — 확인부터 하고, 빠져 있으면 채웁니다. - 생성기 13개 — 원소 타입의 세 멤버와, 원소를 도는 링킹.
- 승격 — 그 패스에서 레코드 멤버 제외를 걷어냅니다.
- 계측 — 샘플 재생성. 507개가 어떻게 되는지.
- 게이트 — 6절.
1번을 먼저 하는 이유는 그 throw가 지금 모든 것을 가리고 있기 때문입니다. 지우면 다음에
무엇이 부족한지를 컴파일러와 게이트에 나타납니다 — 짐작으로 순서를 정하는 것보다 낫습니다.
8. 구현 결과
레코드 그룹의 세 가지 형태
설계가 「레코드 배열」 하나를 상정했는데, 원소 번호가 앉는 자리가 셋입니다. 이것이 이 개정에서 가장 많은 비용이 든 자리입니다 — 하나를 처리한 생성기가 나머지 둘을 우연히 처리하지는 않습니다.
| 형태 | 원소 번호의 위치 | 접근 |
|---|---|---|
| 레코드의 배열 | 그룹 | slot[j].itemId |
| 레코드 하나 | 없음 | main.itemId |
| 멤버가 배열인 레코드 | 멤버 | slots.itemId[j] |
키의 이름은 멤버에 붙고 첨자보다 앞에 옵니다 — slots.itemId_index[j]이지
slots.itemId[j]_index가 아닙니다. 후자는 표현식이 아닙니다. 어느 형태인지는 뷰가
정하고 템플릿은 모릅니다.
언어별 분기 — 원소 안에 두는 것
와이어는 어느 쪽이든 같으므로, 다른 것은 각 언어가 이미 참조를 적던 방식입니다. 통일하지 않고 그 관례를 따랐습니다.
| 언어 | 원소 안에 두는 것 |
|---|---|
| C#·TypeScript·C·C++·PHP·Java·Kotlin·Dart·Go·Python·Ruby | 해석된 행 · 키 — 「해석되었는가」는 널 포인터가 나타냅니다 |
| Rust·Unreal | 키만 — 링킹 자체가 없습니다 |
첫 줄은 처음에 둘로 갈려 있었습니다. C#·TypeScript가 셋째로
bool플래그를 두었고, 그것이 키 검사와 같은 값이어서 없앴습니다 — 참조의 해석 여부.
Rust와 Unreal에 링킹이 없는 것은 이 개정의 미완이 아니라 그 두 언어가 레코드 밖의
참조에도 이미 적용하고 있던 판단입니다. 빌림 그래프와, GC가 추적하지 않는 USTRUCT 안의
원시 포인터가 그 이유이고 타입 주석에 적혀 있습니다. 거기서 멤버가 참조라는 사실이 바꾸는
것은 이름과 키의 타입뿐이며, 이름은 바뀌어야 합니다 — 숫자를 담은 itemId는 행으로
읽힙니다.
픽스처가 드러낸 결함 5개
넷은 이 개정보다 오래된 것입니다.
| 무엇 | 왜 검출되지 않았는가 |
|---|---|
IsElementFilled가 그룹의 멤버를 돌아 컬럼이 없는 레코드 멤버에서 범위를 벗어남 | nested-deep이 같은 형태인데, 첫 멤버가 모든 로우가 채우는 잎이라 그 앞에서 반환됨. 첫 멤버가 레코드인 그룹이면 누구든 걸립니다 |
| 참조 키를 대부분의 언어가 int32로 읽음 | reference-keys 시나 리오가 C#·TypeScript만 내므로, 나머지는 int가 아닌 키를 요구받은 적이 없습니다. 자세한 것은 아래 |
Ruby 원소 클래스에 키의 attr_accessor가 없음 | 컴파일되고 첫 로우에서 실패합니다 |
Python 원소 클래스의 __slots__에 키가 없음 | 같음 |
| PHP의 런 경로가 멤버 대신 그룹을 지칭 | 이 개정이 다른 언어에서는 고쳤고 PHP만 빠뜨렸습니다 |
되읽기가 컴파일보다 뒤에 있어야 하는 이유가 셋째·넷째입니다. 컴파일은 통과하고 첫 로우에서 실패합니다.
둘째 결함의 크기 — 참조 키의 int32 가정
이것 하나가 나머지 넷을 합친 것보다 컸습니다. 참조는 대상의 키를 그대로
싣는데, 그렇게 읽는 언어는 C#과 TypeScript뿐이었습니다.
언어마다 6곳에 int32가 하드코딩되어 있었고, 어느 언어가 어느 자리를 갖는지는 다른
언어를 보고 짐작할 수 없었습니다.
| 자리 | 무엇 |
|---|---|
| 커서 읽기 | 그리고 참조가 커서를 타는지 자체 — uuid 키는 uuid 컬럼과 마찬가지로 커서 경로가 없습니다 |
| 허용 element | 익스포터는 키의 element를 쓴 지 오래인데 리더는 ElementI32만 받게 되어 있었습니다 |
| 런 인코딩 | 호출·값이 담기는 지역변수·그 지역변수를 쓰는 대입 줄, 셋이 각각 |
| 키의 초기화 | = 0은 int의 값이지 FString·FGuid·tabbit::Uuid·PHP의 클래스 타입 프로퍼티의 값이 아닙니다 |
| 평범한 읽기 호출 | 커서를 타지 않는 그 키 하나를 위해 |
언리얼이 가장 나빴고, 이미 있던 게이트가 볼 수 없는 것이었습니다. UHT는 헤더를
리플렉션 목적으로 파싱하지 컴파일하지 않으므로 FString ClipIdIndex = 0;을 통과시킵니다.
게이트
record-ref 픽스처가 여섯 테이블로 위의 세 형태와 그 밖의 세 가지를 함께 답니다 —
두 단계 안쪽의 참조, 문자열로 키가 잡힌 대상, 그리고 로우마다 길이가 다른 자르는 배열.
Loadout은 한 원소가 같은 테이블을 가리키는 참조 둘을 갖는데, 키를 원소 안에 둔
이유가 그것입니다. Seal과 Badge는 나중에 붙었습니다 — 위의 둘째 결함이 문자열 키에서
드러난 뒤, uuid 키와 런 경로까지 요구하기 위해서입니다.
| 게이트 | 무엇 |
|---|---|
| 골든 | 모든 언어의 트리. 형식이 안 바뀌었다는 것을 바이트로 확인합니다 |
| 컴파일 또는 실행 | 언어마다 하나. C#은 되읽기까지 |
| TypeScript 왕복 | 이름 있는 JSON · compact JSON · 바이너리 세 경로가 같은 값을 내는지 |
샘플 재생성은 뒤로 미뤘습니다. 6절이 게이트로 적어 두었는데, 그 워크북들에는 이
개정과 무관한 시트 문제가 남아 있어 전 세트가 변환되지 않습니다. 검증은 픽스처로 충분하고,
계측은 시트가 정리된 뒤에 하는 편이 정확합니다. 지금까지 확인된 것은 주요 10개 테이블
빌드에서 12개 선언이 승격되었다는 것이고, 그중 하나가 1절의 거부 메시지가 지목한
GearSlot입니다.
형식
한 바이트도 안 바뀌었습니다. 다른 16개 시나리오의 골든이 움직이지 않았습니다.