작성 커널 — 두 번째 작성 표면 구축하기
고급 내용. SheetForge의 엔진 위에 자신만의 작성 UI(예: 노드 그래프 캔버스)를 구축하고자 하는 에셋/툴 제작자를 위한 페이지다. 데이터 스튜디오를 사용하는 게임 팀은 이 페이지가 필요하지 않다.
작성 창은 엔진이 아니다. 데이터 스튜디오 — 그리고 그 곁의 브라우저 앱 — 는 창(window)과 무관한 작성 커널의 소비자일 뿐이다.
이들이 하는 모든 것은 세 번째 표면도 동등하게 다룰 수 있는 공개 타입을 통해 구동된다: 스테이징, 검증, 반영 오케스트레이션, undo 경계, 재임포트. 이미 둘이 그렇게 하고 있으며, 이는 이 이음매가 열망이 아니라 실재한다는 실질적인 증거다.
전체 표면을 새로 짓기 전에, 확장 지점이 이미 그 필요를 다루고 있는지 먼저 확인하라. 플러그인은 창을 아예 소유하지 않고도 기본 제공 창에 동사(verb)·패널·배지·셀 위젯을 추가할 수 있으며, 이는 데이터로 서술되므로 에디터 그리고 브라우저 양쪽에서 렌더링된다 — 플러그인 작성 §4.16 참고. 이 페이지는 자신만의 캔버스를 원하는 경우를 위한 것이다.
소비자 시뮬레이션 테스트 어셈블리(SheetForge.Tests.Consumer — Core나 Editor로의 InternalsVisibleTo가 없음)는 공개 API만을 대상으로 가상의 작성 표면을 처음부터 끝까지 구현한다. 필요한 멤버가 internal이었다면 그 어셈블리는 컴파일되지 않았을 것이므로(CS0122), 이는 아래에서 설명하는 표면의 실행 가능한 명세 역할을 한다.
세 가지 객체로 이루어진 엔진
┌─────────────────────┐ ┌──────────────────────────┐ ┌───────────────┐
│ AuthoringSession │────▶│ AuthoringDispatcher │────▶│ BaselineStore │
│ (staging state) │ │ .Reflect() │ │ (round-trip │
│ │ │ (the full cycle) │ │ snapshots) │
└─────────────────────┘ └────────────┬─────────────┘ └───────────────┘
│ binds
┌────────────▼─────────────┐
│ AuthoringDispatchCallbacks│
│ (view concerns — YOUR UI) │
└──────────────────────────┘AuthoringSession — 스테이징 상태
[Serializable] 일반 클래스다(의도적으로 ScriptableObject가 아니다): 이를 EditorWindow의 [SerializeField] 필드에 담아 두면 Unity 네이티브 Undo 스냅샷과 도메인 리로드 생존을 공짜로 얻는다 — 기본 제공 창의 Ctrl+Z 뒤에 있는 것과 동일한 메커니즘이다.
이는 모든 스테이징 상태를 소유한다:
- 셀 편집(
Edits), 신규 행(NewRows), 구조 작업(StructOps); - 탭별 순서 변경(
Reorders), 탭 이름 변경(TabRenames); - baseline 앵커, 격리된 편집.
그 상태 위에 변경/조회 API를 노출한다:
SetStaged(...)— 셀 편집을 스테이징한다. 편집은 논리적 주소(탭 · RecordId · 필드)를 가지며, 물리적 행 순번은 반영 직전에 다시 해석되는 파생 캐시다.ResolveBaselineEdits(provider)— 모든 편집을 현재 baseline에 대해 다시 앵커링한다. 해석 가능한 편집은 그대로 진행되며, 해석 불가능한 세 가지 경우(외부 이름 변경 / 외부 삭제 / 키 충돌)는IsolatedEdits로 옮겨진다 — 반영에서 제외되고, 배지로 표시되며, 결코 조용히 버려지지 않고, 결코 세션을 막지 않는다.- Baseline 읽기 표면:
TabNames,TryGetBaselineTable(tab, out SheetTable)— 파서를 직접 건드리지 않고도 타입이 지정된 스키마 접근(TypeToken,@desc,@overlap)이 가능하다. EffectiveStructOps()/PendingStructCount()— 합성된 정본 구조 작업 뷰.- 리맵 훅(
RemapFieldName/RemapRecordId/RemapTab)은 이름 변경 전반에 걸쳐 스테이징된 상태의 일관성을 유지한다. LastProjectionResult는 최신 프로젝션을 캐시한다.
AuthoringDispatcher — 반영 오케스트레이션
var dispatcher = new AuthoringDispatcher(session, callbacks, baselineStore);
dispatcher.Reflect(); // the entire cycle, one callReflect()는 다음을 순서대로 실행한다:
- 사전 검증
- 소스별 반영 — 로컬은 정교한 쓰기, Google은 안전한 재작성, 커스텀 프로바이더는 자신만의 대상
- 유지 상태 정리
ClearUndo확정 경계- 리포트를 동반한 자동 재임포트
또한:
BuildProjectionResult()— 현재 스테이징 상태를ImportResult로 나타내는 부작용 없는 프로젝션(마치 반영된 것처럼 검증). 실시간 에러 배지에 사용한다.- 공개
Session/Callbacks/Baselines— 커스텀 소스 프로바이더가 자신의 반영 대상을 조립할 때 이를 사용한다.
AuthoringDispatchCallbacks — UI의 계약
디스패처가 모든 뷰 관련 사항에 대해 호출하는 13개의 일반 델리게이트 묶음이다: ResolveSettings, 확인 대화상자(ConfirmKeyRenames, ConfirmTabRenames 등), RenderReport(Action<ImportReport> — null 허용, 관찰용일 뿐), PushApprover, TriggerReimport, ClearUndo, Rebuild 등.
내장 Local/Google 소스에 특화된 14개의 대화상자 델리게이트는 별도의 선택적 BuiltInSourceDialogs 묶음에 있다. 외부 표면이나 프로바이더는 이들을 바인딩할 필요가 전혀 없다.
기본 제공 창은 대화상자를 표시하는 기본값을 바인딩하며, 사용자의 캔버스는 자신만의 것을(또는 아무 동작도 하지 않는 것을) 바인딩한다. 엔진은 결코 UI를 직접 그리지 않는다.
그래프 소재
"노드 = 레코드, 엣지 = 참조 ∪ 선언"이라는 프로젝션을 위해:
ReferenceScanner(Core) — 모든 테이블에 걸쳐 참조 발생을 열거하는 단일 진실 공급원이다: 스칼라, 리스트 요소, 명시적 기본값. 참조 검증기가 사용하는 것과 동일한 열거이므로, 사용자의 그래프와 검증은 구조적으로 일치한다.Scan(tables)/ScanTable/ScanField/IsReferenceField.IEdgeContributor/EdgeSpec/EdgeContributorRegistry(Core) — 도메인 플러그인은 스캐너가 볼 수 없는 엣지(커스텀 타입 값 내부,type컬럼 링크, 페이로드 레코드가 있는 레코드 엣지)를 선언한다. Editor의PluginRegistry.BuildEdgeContributors를 통해 이들을 수집한다.ReferenceIndex/RecordEdge(Core) — 데이터 스튜디오 자신의 캔버스가 구동하는 조립된 스냅샷이다:Build(...)는 스캔된 참조와 기여자 엣지를 한 번에 병합하고, 그다음OutEdges/InEdges/InCount가 레코드당 O(1)로 답한다. 전체 멤버 목록은 API 참조에 있다.IRecordCanvasAugmenter/CanvasAugmentBuilder(Core) — 탭별 오버라이드 계약이다. 도메인 팩이 스튜디오의 캔버스를 확장하는 것과 동일한 방식으로 사용자의 캔버스를 확장하게 하고 싶다면 사용한다(가상 노드, 추가 엣지, 레이어와 표시 힌트).ProjectionErrorMapper(Editor, 순수) — 프로젝션 에러의 물리적 좌표(탭/행/필드)를 논리적 주소(탭/RecordId/필드)로 매핑하므로, 에러 배지를 행 번호가 아니라 노드에 고정할 수 있다.
지원 요소
| 타입 | 사용자의 표면이 이를 사용하는 용도 |
|---|---|
ImportEvents | 둘 다 공개 계약인 두 개의 버스. ImportCompleted(ImportCompletedArgs: Tabs · BakeFolder)는 자동 체인이 베이크까지 끝까지 실행됐을 때 발생하므로, 구독자는 베이크된 에셋을 읽을 수 있다. BaselineUpdated(BaselineUpdatedArgs: Tabs · Quarantined)는 시트 스냅샷이 저장될 때마다 — 검증에 실패한 실행을 포함해 — 발생하며, 실패한 시트를 보여주고 사람들이 고칠 수 있게 하고 싶은 표면이 구독하는 것이 바로 이것이다. 자신의 뷰가 시트와 베이크된 값 모두를 보여준다면 둘 다 구독한다. OnDisable에서 대칭적으로 구독을 해제한다. |
IPipelineObserver | 알아야 할 대상이 창이 아니라 플러그인이라면, 이쪽이 더 가벼운 경로다: 옵저버를 등록하면 각 임포트 사이클이 끝날 때마다 불변 PipelineRunView를 받으며, 에디터 의존성이 전혀 없다 — 브라우저 호스트에서도 동작한다. 플러그인 작성 §4.17 참고. |
RecordIdMinter | 신규 레코드에 대한 id를 제안한다 — 접두사 감지 + 충돌 안전 고유화. 의도적으로 자동 번호 매기기가 아닌 제안 API다. |
EphemeralSoApply | 스테이징된 값을 베이크된 SO 위에 임시로 미리보기한다(재임포트가 복원한다). 계산 가능한 부분집합을 적용하며, 대기 중인 컬럼과 파싱 실패에 대해서는 건너뜀 사유를 반환한다. 기본 제공 UI에서는 더 이상 아무것도 이를 구동하지 않으므로, 이 미리보기를 원하는 표면이 그 버튼을 직접 소유한다. |
KeyRenamePlanner | 키 이름 변경(3단계: 추출 / 전파 / 수술)을 계획한다. 기본 제공 창과 동일한 방식이다. 탭 이름 변경 확인은 대신 공개 ConfirmTabRenames 콜백을 거친다. |
SourceProviderRegistry | 설정 UI와 동일한 방식으로 활성 소스 프로바이더를 해석한다. |
커널이 강제하는(그리고 상속받는) 기본 규칙
- 시트는 정본으로 유지된다 — 사용자의 표면은 스테이징하고 반영할 뿐, 결코 SO에 기록하지 않는다.
- 검증 후 반영 — 사전 검증이 실패하면
Reflect()는 아무것도 기록하지 않는다. - 조용한 손실 없음 — 해석 불가능한 편집은 사유와 함께 격리된다. 확인 절차는 사용자의 콜백을 거친다.
- Undo가 네이티브하게 통합된다 — 세션을 직렬화된 필드에 보관하고 창에 undo 스냅샷을 등록한다.
ClearUndo가 반영 경계를 표시한다. - 도메인 무관 — 커널은 도메인 어휘를 전혀 포함하지 않는다(가드 테스트됨). 사용자의 도메인은 커널 수정이 아니라 플러그인 계약을 통해 도착한다.