跳至主要內容
SheetForge

編寫核心——建構第二個編寫介面

進階內容。適合想在 SheetForge 引擎之上建構自己編寫 UI(例如節點圖畫布)的素材/工具開發者。使用 Data Studio 的遊戲團隊不需要閱讀本頁。

編寫視窗並不是引擎。Data Studio——以及與其並存的瀏覽器應用——都只是一個與視窗無關的編寫核心的使用端

它們所做的一切,都是透過公開型別驅動的,而第三個介面同樣也能以相同方式驅動:暫存、驗證、反映調度、復原邊界、重新匯入。已經有兩個介面在這麼做了,這正是這道介面確實存在、而非紙上談兵的實際證明。

在建構一整個編寫介面之前,先確認是否已有現成的擴充點涵蓋你的需求。一個外掛可以在完全不需要擁有自己視窗的情況下,為隨附的視窗新增動作、面板、徽章與儲存格元件,並以資料的形式描述它們,讓它們同時能在編輯器瀏覽器中繪製——請參閱外掛開發 §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 讀取介面:TabNamesTryGetBaselineTable(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 call

Reflect() 會依序執行整個循環:

  • 預檢驗證
  • 依來源進行反映——本機採精準寫入、Google 採安全改寫、自訂提供者則使用你自己的目標
  • 保留狀態的清理
  • ClearUndo 確認邊界
  • 附帶報告的自動重新匯入

此外:

  • BuildProjectionResult()——將目前的暫存狀態投影為一個無副作用ImportResult(彷彿已反映般進行驗證)。可用於即時錯誤徽章。
  • 公開的 Session / Callbacks / Baselines——自訂來源提供者會使用這些來組裝自己的反映目標。

AuthoringDispatchCallbacks——你的 UI 合約

這是一組共 13 個一般性委派方法,調度器會針對每一項視圖相關的事務呼叫它們:ResolveSettings、確認對話框(ConfirmKeyRenamesConfirmTabRenames……等)、RenderReport(一個 Action<ImportReport>——可容許 null,它只是用於觀察)、PushApproverTriggerReimportClearUndoRebuild 等等。

內建 Local/Google 來源專屬的 14 個對話框委派方法,則獨立收納於另一個選用(opt-in)的 BuiltInSourceDialogs 套組中——外部介面或提供者絕不需要綁定它們。

隨附的視窗會繫結會顯示對話框的預設實作;你的畫布則可以綁定自己的實作(或什麼都不做)。引擎本身絕不會自行繪製任何 UI。

圖形素材

若要建構「節點 = 記錄,邊 = 參照 ∪ 宣告」的投影:

  • ReferenceScanner(Core)——列舉所有資料表中參照出現位置的唯一真實來源:純量、清單元素、明確的預設值。這與參照驗證器所使用的枚舉方式完全相同,因此你的圖形與驗證結果在架構上必然一致。Scan(tables) / ScanTable / ScanField / IsReferenceField
  • IEdgeContributor / EdgeSpec / EdgeContributorRegistry(Core)——領域外掛可藉此宣告掃描器看不到的邊(存在於自訂型別值內部、type 欄連結、帶有負載記錄的記錄邊)。可透過 Editor 的 PluginRegistry.BuildEdgeContributors 蒐集它們。
  • ReferenceIndex / RecordEdge(Core)——Data Studio 自身畫布所運作的組裝完成快照:Build(...) 會將掃描到的參照與貢獻者邊合併一次,之後 OutEdges / InEdges / InCount 便能以每筆記錄 O(1) 的複雜度回答查詢。完整成員清單請見API 參考
  • IRecordCanvasAugmenter / CanvasAugmentBuilder(Core)——逐分頁的覆寫合約,適用於你希望領域套件以擴充 Studio 畫布的相同方式(虛擬節點、額外的邊、圖層與顯示提示)來擴充你自己畫布的情況。
  • ProjectionErrorMapper(Editor,純函式)——將投影錯誤的實體座標(分頁/列/欄位)對應到邏輯位址(分頁/RecordId/欄位),讓你可以把錯誤徽章固定在節點上,而不是列號上。

輔助元件

型別你的編寫介面可用它來做什麼
ImportEvents兩條匯流排,皆為公開合約ImportCompletedImportCompletedArgsTabs · BakeFolder)會在自動連鎖流程完整跑完烘焙之後觸發,因此訂閱者可以讀取烘焙後的資源。BaselineUpdatedBaselineUpdatedArgsTabs · Quarantined)則會在每次試算表快照被儲存時觸發——包括驗證失敗的一次執行——這正是一個編寫介面若想顯示失敗的試算表、並讓使用者修正它們時,所應該訂閱的事件。若你的畫面同時顯示試算表烘焙後的數值,請兩者都訂閱;並記得在 OnDisable 中對稱地取消訂閱。
IPipelineObserver如果需要知道結果的是一個外掛而不是一個視窗,這是更輕量的路徑:註冊一個觀察者,即可在每次匯入循環結束時收到一個不可變的 PipelineRunView,完全不依賴編輯器——在瀏覽器主機中同樣可以運作。請參閱外掛開發 §4.17
RecordIdMinter為新記錄建議 id——前綴偵測 + 避免衝突的唯一化處理。這是一個建議性質的 API,刻意不做成自動編號。
EphemeralSoApply將暫存的數值暫時預覽套用到烘焙後的 SO 上(重新匯入會將其還原)。只會套用可計算的部分;對於待處理的欄與剖析失敗的情況,會回傳略過的原因。隨附的 UI 中已經沒有任何東西會驅動它了,因此想要這項預覽功能的編寫介面,必須自行提供對應的按鈕。
KeyRenamePlanner規劃鍵值重新命名(3 階段:擷取/傳播/手術式改寫),與隨附視窗的做法相同。分頁重新命名的確認流程則改為透過公開的 ConfirmTabRenames 回呼進行。
SourceProviderRegistry以與設定 UI 相同的方式,解析目前使用中的來源提供者。

核心強制執行(並由你繼承)的基本規則

  • 試算表維持其權威地位——你的編寫介面負責暫存與反映;它絕不會寫入 SO。
  • 先驗證,再反映——如果預檢失敗,Reflect() 就不會寫入任何內容。
  • 絕不靜默遺失——無法解析的編輯會連同原因一起被隔離;確認動作則會透過你的回呼函式傳遞。
  • 原生整合的 Undo——將工作階段保存在一個已序列化的欄位中,並在你的視窗上註冊復原快照;ClearUndo 會標示出反映的邊界。
  • 與領域無關——核心完全不含任何領域詞彙(並有防護測試驗證)。你的領域是透過外掛合約加入,而不是透過修改核心。

相關頁面

  • API 參考——本頁提及的每一項內容的簽章
  • 外掛開發——你的領域會與核心一同使用的合約
  • Data Studio——你的編寫介面所複製或取代的行為