다중 대상 참조의 접근자
되돌렸습니다 — 참조가 내는 이름 §6.
foreign의 대상은 테이블 하나 이고, 여러 테이블 중 하나여도 되는 값은 참조가 아니라 검사입니다(refs=A;B). 이 문서가 세운 검사는 그대로 살아 있고, 없어진 것은 승격과 그 위에 선 생성 표면입니다. 아래는 그 결정에 이르기까지의 기록입니다.상태: 됨 — A단계와 B단계 모두. 모든 언어, HTML, 레코드 멤버까지
다중 대상 참조 8절의 구현 순서 7번이 이 문서입니다. 레코드 안의 참조가 그 목록의 앞 항목을 끝냈고, 이것이 그 목록의 마지막이었습니다
한 컬럼의 값이 여러 테이블 중 어느 하나의 행일 때, 모델은 그 대상들을 이미 알고 있고 검사도 돌고 있습니다. 생성 코드만 그것을 모릅니다. 소비하는 쪽이 받는 것은 숫자 하나이고, 어느 카탈로그의 행인지는 사람이 기억해야 합니다.
1. 문제 — 모델은 알고 생성기는 모릅니다
승격은 끝나 있습니다. 필드가 대상 이름 목록을 들고, 대상 테이블이 해석되고, 값이 대상의 키 타입으로 좁혀집니다. 그런데 그 뒤가 없습니다.
| 층 | 다중 대상 |
|---|---|
| 제약 행 읽기 | 됨 |
| 필드가 대상 목록을 듦 | 됨 |
| 승격 — 적힌 테이블이 전부 이 빌드에 있을 때 | 됨 |
| 대상 테이블 해석 | 됨 |
| 대상끼리 키 타입이 같은지 | 됨 |
| 대상끼리 id가 겹치지 않는지 | 됨 |
| 값이 대상 중 하나에 있는지 | 됨 |
| 와이어가 대상의 키 타입으로 실림 | 됨 |
| 생성 코드의 접근자 | 없습니다 |
| HTML 문서의 링크·역참조·그래프 | 없습니다 |
왜 지나가는지
IsRef는 「정확히 한 레코드로 풀린다」를 뜻하고, 그것을 지키려고 승격 패스가 다중 대상에서는
단수 이름을 비워 둡니다. 그 결정 자체는 옳습니다 — ResolvedRefTable is null의 뜻을 안 바꾸는
것이 그 설계가 산 것입니다. 문제는 아무도 목록 쪽을 안 본다는 것입니다.
RefTableNames·ResolvedRefTables·IsMultiRef를 읽는 곳이 src/ 전체에서 한 줄뿐입니다.
그 한 줄이 HTML 생성기의 RefTargetsOf인데, 그것을 부르는 여섯 자리가 전부 IsRef 게이트
뒤에 있어 도달하지 않습니다. 다중 대상을 위해 쓴 코드가 다중 대상에서만 안 돌고 있습니다.
검 사는 돕니다. all 빌드에서 컬럼 273개를 132만 행에 대조하여 위반 0건이고, 대상을 다
갖추지 못한 컬럼 125개는 판정하지 않고 경고합니다. 즉 잃은 것은 정확성이 아니라 표현입니다.
2. 실측 — 무엇이 얼마나 있는지
승격 패스에 계측을 넣고 이전 대규모 코퍼스의 전량 빌드를 돌린 결과입니다.
승격된 것 — 접근자만 없는 컬럼
| 대상 개수 | 컬럼 |
|---|---|
| 2 | 18 |
| 3 | 4 |
| 5 | 1 |
| 6 | 3 |
| 8 | 1 |
| 10 | 1 |
| 11 | 2 |
| 13 | 1 |
| 합계 | 31 |
넓은 것부터 ChoiceBox.ChoiceBoxId(13) · Offer.CeilingCashShopBoxIconId(11) ·
DrawGameReward.RewardId(10) · EventShop.ProductId(8)입니다.
막혀 있는 것 — 레코드 그룹의 멤버
| 경우 | 컬럼 |
|---|---|
| 이름 있는 레코드 멤버, 대상이 전부 있음 | 156 |
| 이름 있는 레코드 멤버, 대상이 이 빌드에 없음 | 22 |
| 레코드 밖, 대상이 이 빌드에 없음 | 13 |
| 합계 | 191 |
156개가 그 홀드백 하나 때문에 서 있습니다. 컬럼 개수와 생성될 멤버 개수는 다릅니다 — 레코드 배열의 원소는 한 멤버를 공유하므로, 156개 컬럼이 멤버로는 31개입니다. 위의 승격된 컬럼 31개와 수가 같은 것은 우연입니다.
생성 코드가 늘어나는 양
| 무엇 | 얼마 |
|---|---|
| 승격된 컬럼의 대상별 프로퍼티 | 124 |
| 레코드 멤버의 대상별 프로퍼티 | 150 |
| 합계 | 274 |
로우 하나가 드는 양 — 여기가 설계를 바꿉니다
프로퍼티 개수와 달리, 저장 슬롯은 원소마다 생깁니다. 아래는 대상마다 슬롯을 하나씩 두는 경우이고, 선언 전체를 합친 수입니다 — 각 수는 그 선언이 있는 테이블의 로우 하나가 듭니다.
| 무엇 | 슬롯 |
|---|---|
| 승격된 컬럼 | 124 |
| 레코드 멤버 | 862 |
가장 넓은 자리가 RewardFixed.RewardFixed.Id입니다 — 대상 16개 × 원소 20개 = 로우당 320
슬롯. 그 열여섯이 무엇인지 보면 왜 이런 형태인지 알 수 있습니다.
Point, Item, ExpeditionSupply, Market, CharGear, Ship, Roster, Recipe,
GearSlot, Shield, TaxFreePermit, PortraitSkin, CosmeticSkin, Emblem, Pet,
SeasonReward
「보상 종류」 다형성입니다. 같은 목록이 LootPool · CashShopBanner ·
SalvageRewardGroup에서 되풀이됩니다. 대상이 둘인 컬럼과 열여섯인 컬럼은 개수만 다른 것이
아니라 쓰임이 다른 선언이고, 설계가 뒤쪽을 견뎌야 합니다.
그리고 대상끼리 겹치지 않습니다
all 빌드 전체에서 키 겹침 0건입니다. 이것은 관찰이 아니라 계약입니다 — 겹치면
CheckKeyBandsDoNotOverlap이 에러를 냅니다. 존재 검사와 합치면 이렇습니다.
셀에 값이 있으면, 그 값은 대상 중 정확히 하나의 행 id입니다.
3절이 이 계약 위에서 성립합니다.
3. 결정 — 공개 표면은 대상별, 저장은 슬롯 하나
8절이 정한 공개 표면을 그대로 씁니다. 바꾸는 것은 그 뒤에 무엇을 담는가입니다.
| 무엇 | 대상마다 슬롯 | 슬롯 하나 + 식별자 |
|---|---|---|
| 슬롯 합 — 레코드 멤버 | 862 | 156 |
| 슬롯 합 — 승격된 컬럼 | 124 | 31 |
RewardFixed 로우 하나 | 320 | 20 |
| 언제나 비어 있는 슬롯 | 15/16 | 없음 |
| 「어느 카탈로그인가」 | 프로퍼티 16개를 널 검사 | 식별자 하나 |
| 공개 프로퍼티 | 같음 | 같음 |
대상마다 슬롯을 두면 그중 하나만 채워집니다. 2절의 계약이 그렇게 정해 두었으므로, 대상이 열여섯인 컬럼은 슬롯 열여섯 중 열다섯이 언제나 비어 있습니다. 그것은 데이터의 성질이지 우연이 아니므로, 담는 쪽이 그것을 알고 있어야 합니다.
C#에서는 이것이 크기 문제이기도 합니다. 레코드 원소가 struct이므로, 원소에 참조 열여섯
개를 더하면 원소의 크기가 그만큼 커지고 복사될 때마다 따라옵니다. 슬롯 하나면 원소는
포인터 하나와 작은 정수 하나만 늘어납니다.
담는 것
| 무엇 | 타입 |
|---|---|
| 키 | 대상의 기본 인덱스 타입 — 지금 그대로 |
| 해석된 행 | 대상 무관 슬롯 하나 — 그 언어의 「무엇이든」 타입 |
| 식별자 | 어느 대상이었는가. 아무것도 아니면 None |
합 타입이 공개 표면에 안 나옵니다. 8절이 합 타입을 피한 근거가 유지됩니다 — 대상 무관 슬롯은 내부이고, 소비하는 쪽이 보는 것은 언제나 타입이 하나인 프로퍼티입니다.
식별자는 더한 기능이 아니라 슬롯 하나의 대가입니다. 슬롯을 하나만 두면 그 안에 어느 테이블의 행이 들었는지 말해 주는 것이 있어야 캐스트가 안전하고, 식별자가 그 태그입니다. 대상마다 슬롯을 두는 설계에서는 필요하지 않습니다 — 그쪽은 「단일 대상 참조 N개」와 완전히 같은 것이고, 그것이 이 설계를 읽을 때 가장 먼저 떠오르는 형태입니다. 아래에 무엇을 사고 무엇을 낸 것인지 적어 둡니다.
고려하고 버린 것
- 대상마다 슬롯. 위 표. 대상이 둘일 때는 차이가 없고 열여섯일 때 8배입니다.
RewardFixed(대상 16 · 원소 20 · 약 25,000행)로 재면 64MB 대 8MB이고, 64MB 중 60MB가 영구히null입니다 — 16개 중 15개는 절대 채워지지 않기 때문입니다. C#에서는 그 포인터들이struct원소 안에 인라인이라 원소를 읽을 때마다 복사됩니다. A단계에서는 이 절감이 거의 0입니다 — 승격된 31개는 대상이 대부분 둘이고 원소 배수가 없습니다. 이 결정이 값을 갖는 것은 B단계입니다. - 담지 않고 접근할 때 조회. 슬롯이 0이 되지만 로우가 접근자를 되가리켜야 하고, 지금 생성되는 레코드에는 그 되가리킴이 없습니다. 참조를 로드 뒤 한 번 잇는 것이 이 도구의 방식이고, 그것만 다르게 하면 배울 것이 하나 늘어납니다.
- 식별자를 안 내는 것. 대상이 둘이면 널 검사 두 번으로 되지만, 열여섯이면 소비하는 쪽이 16단 분기를 씁니다. 식별자는 링킹이 이미 알고 있는 것이고, 안 내는 것은 아는 것을 버리는 일입니다.
4. 코어 표기 — 대상을 여럿 적는 법
코어가 표현할 수 있는 것을 코어 표기가 적을 수 없습니다. 모델은 대상 목록을 들고 검사까지
하는데, 그 목록에 둘 이상을 넣는 길이 어떤 프로젝트 레이아웃의 제약 행 하나뿐입니다.
코어 레이아웃의 foreign은 detail-type에 RefTable[.RefFieldName] 하나만 받습니다.
두 가지가 걸립니다.
- 게이트를 세울 자리가 없습니다. 골든 시나리오는 전부 코어 레이아웃이고, 거기에 다중 대상을 적을 표기가 없습니다. 그러면 이 개정의 판정 기준이 프로젝트 하나의 워크북이 되는데, 그것은 CLAUDE.md가 코어 기능에 대해 금지한 형태입니다.
- 거꾸로 서 있습니다. 「기능은 일반적으로 만들고 이름은 플러그인 쪽에」가 규칙인데, 지금은 기능 자체가 플러그인 쪽에서만 들어옵니다.
결정. foreign의 detail-type이 세로줄로 여럿을 받습니다.
foreign Weapon 대상 하나 — 지금 그대로
foreign Weapon|Armour 둘 중 하나
foreign Weapon.Name 대상의 필드 — 지금 그대로
세로줄인 근거가 넷입니다. 「또는」으로 읽히고 — 이 선언이 뜻하는 것이 그것입니다 — 점 표기와
부딪히지 않으며(점은 대상 안을 가리키고 이것은 대상들을 가릅니다), 역할의
괄호(text(Common))와도 부딪히지 않고, 테이블 이름에 들어갈 수 없는 문자입니다.
- 하나만 적으면 지금과 완전히 같습니다. 길이 1의 목록이고, 기존 시나리오의 골든이 한 바이트도 안 움직입니다.
- 점 표기와 섞는 것은 받지 않습니다.
Weapon.Name|Armour.Name은 8절. - 적힌 대상이 이 빌드에 없으면 거부입니다. 프로젝트 레이아웃의 제약 행은 같은 경우를
경고로 두는데 — 넓은 빌드를 겨냥한 선언일 수 있기 때문입니다 —
foreign은 지금도 없는 대상을 거부하고 그 규칙을 그대로 물려받습니다. 표기마다 규칙을 새로 정하는 것이 아니라 각 표기의 기존 규칙이 유지되는 것입니다.
이 표기가 서면 픽스처를 코어 레이아웃으로 적을 수 있고, 10절의 게이트가 프로젝트 워크북 없이 돕니다.