跳至主要内容
SheetForge

API 参考——公共表面

本页列出了产品各程序集中的每一个公共类型。凡是这里没有列出的,都是刻意设计为 internal 的——公共表面是有意收窄的。

  • CoreSheetForge.Core + SheetForge.Core.Tooling):137 个公共类型(Core:131 个,Core.Tooling:6 个)。Core.Tooling 是仅限编辑器使用的那一半,存放报告、Push 规划等导入期服务——它完全不会随包进入玩家构建。
  • Editor:53 个顶层公共类型及其公共嵌套类型。
  • Runtime:7 个类型,外加生成的输出。

这正是不使用 InternalsVisibleTo 的消费者模拟测试所编译针对的表面。

检测契约(并非类型):另一个资产也可以通过 Editor 程序集自我注册的 SHEETFORGE 脚本编译宏,在编译期检测到 SheetForge 已安装。它是一个,不是公共类型,因此不会列在下方的表格中——见插件开发 ▸ 从其他资产中检测 SheetForge。(与 SHEETFORGE_ADDRESSABLES 不同,后者是一个内部版本宏,只标记 Addressables 包是否存在。)

约定:签名均为简写形式( = 见源码中的 XML 文档);"pure"表示不依赖 UnityEngine,也不涉及 IO。


Core 程序集(SheetForge.Core)——纯 C#

不依赖 UnityEngine,无 IO,无网络,不含任何领域知识。由编译器强制保证:Core 不引用任何东西。

插件注册契约(SheetForge.Core.Plugins

类型种类角色与关键成员
ISheetForgePlugin接口基础的领域插件契约。string Name · RegisterEnums(EnumRegistry) · RegisterCellParsers(CellParserRegistry)
ISheetForgeValidatorPlugin接口用于验证规则的可选附加项。RegisterValidators(DomainValidatorRegistry)
ISheetForgeEdgePlugin接口用于边声明的可选附加项。RegisterEdgeContributors(EdgeContributorRegistry)
ISheetForgeMarkerPlugin接口用于自定义结构标记的可选附加项。RegisterStructuralMarkers(MarkerRegistry)
ISheetForgeTemplatePlugin接口用于"创建工作表"模板的可选附加项。RegisterTemplates(TemplateRegistry)
ISheetForgeGraphPlugin接口用于注册按标签页 Data Studio 画布覆盖的可选附加项。RegisterGraphShapes(GraphShapeRegistry)
ISheetForgeCodeRegistryPlugin接口用于代码持有的引用目标(锁定的虚拟标签页)的可选附加项。RegisterCodeRegistries(CodeRegistryCatalog)
ISheetForgeThemePlugin接口用于窗口色彩预设的可选附加项。RegisterThemes(ThemeRegistry)
ISheetForgeStudioPlugin接口用于声明式创作界面(动作、面板、列徽标、单元格编辑器提示)的可选附加项。RegisterStudioUi(StudioUiRegistry)。位于 Core 而非 Editor 中,因此一次注册就能同时在 UIToolkit 编辑器和浏览器中渲染
ISheetForgeStringsPlugin接口用于按语言注册该包自己 UI 字符串的可选附加项。RegisterStrings(StringOverlayRegistry)。取代了已淘汰的 Editor 侧 ISheetForgeLocPlugin / PluginLocRegistry 组合——那一对只能触达编辑器
ISheetForgePipelinePlugin接口用于注册管线观察者的可选附加项。RegisterPipelineObservers(PipelineObserverRegistry)

组合与兼容性(SheetForge.Core.Plugins

发现过程按宿主区分——编辑器中是 Unity 的 TypeCache,Web 端是浏览器对已上传程序集的扫描。在此之后的一切(实例化、排序、隔离,以及兼容性关卡)都是同一个共享的 Core 功能,这正是让两个宿主不会逐槽位产生偏差的原因。

类型种类角色与关键成员
PluginComposition静态类唯一的程序集处理路径。每个类型一个实例,转换为它实现的每一个契约。两个成员以及诊断分流机制见表格下方
PluginSetsealed 类组装完成的结果——十二个槽位Enums · Parsers · Validators · EdgeContributors · Markers · Templates · GraphShapes · CodeRegistries · Themes · Strings · StudioUi · PipelineObservers。一个新槽位只要加进这里,就能同时触达两个宿主
SheetForgePluginCompatAttributesealed 特性(程序集级)[assembly: SheetForgePluginCompat(SheetForgePluginFormat.Current, MinHostVersion = "…", PluginVersion = "…")]int FormatVersion · string MinHostVersion(数字点分格式比较;null/空 = 无要求) · string PluginVersion(仅供展示,从不参与比较)。无需实例化任何东西即可读取,并且是按程序集判定的——一个被拒绝的程序集会失去它全部的注册内容,而不是半加载。缺省 = Minimum 代,无宿主要求
SheetForgePluginFormat静态类代数常量:const int Current · const int Minimum。只有当插件格式本身被替换时才会推进——纯粹的增量式增长会让这个数字保持不变

PluginComposition——两个成员:

  • IReadOnlyList<Type> ContractTypes——发现过程的过滤器。它的顺序是固定的,因为它决定了诊断信息出现的顺序。
  • PluginSet Compose(IReadOnlyList<Type> candidateTypes, string hostVersion, ErrorCollector errors, ICollection<string> failures, Func<string,bool> isProductKey = null)——组装调用本身。

两类问题被刻意区分开来。注册冲突和兼容性拒绝会成为 errors 中的结构化诊断信息;实现层面的错误——构造失败、一个抛出异常的回调——则会成为 failures 中的英文提示行,传入 null 会丢弃它们。

尾随的 isProductKey 判定函数,是字符串覆盖层"插件不得覆盖产品键"这条规则得以在 Core 完全看不到语言表的情况下被强制执行的方式:规则存在于这里,宿主只提供素材。省略这个判定函数,就只会跳过这一条规则。

注册表(SheetForge.Core.Model / .Validation / .Edges

类型角色与关键成员
EnumRegistryenum 名称 → CLR 类型(代码生成所需素材)。Register<TEnum>() · Register(name, memberNames) · TryGetMembers · TryGetClrTypeName · TryGetClrAssemblyName · RegisteredEnumNames。另外三个成员的详情见表格下方
CellParserRegistry类型名称 → 单元格解析器(开闭式)。重复注册会抛出异常。Register(ICellValueParser) · TryGet · TryGetCustomRenderer · RegisteredTypeNames · RegisterWrapper(ICellWrapperType) · TryGetWrapper · RegisteredWrapperNames(包装类型)
DomainValidatorRegistry只追加的验证器列表,保持顺序。Register(IDomainValidator) · Validators
EdgeContributorRegistry只追加的贡献者列表,保持顺序。Register(IEdgeContributor) · Contributors
MarkerRegistry标记名称(不带 @) → 自定义结构标记。与内置标记(SheetSyntax.ReservedMarkers@name/@type/@desc/@overlap/@style/@enum/@loc)冲突 / 重复 / 非法标识符都会抛出异常。Register(IStructuralMarkerDefinition) · TryGet · IsEmpty · RegisteredMarkerNames · AppendMarkerTokens
TemplateRegistry"创建工作表"模板的键 → 模板。空/重复的键、空的展示名、零标签页、空的标签页 TSV 都会抛出异常。Register(DataTemplate) · TryGet · Templates · IsEmpty

EnumRegistry——三个成员的详情:

  • EnumRegistry(EnumRegistry parent)——一个子实例,读取时会透传到父实例,但注册时只作用于自身。父实例持有整个域重载期间由插件注册的 CLR enum,子实例持有本次导入中由工作表定义的那些,因此一次导入永远不会修改共享缓存。注册一个父实例已经拥有的名称会直接抛出异常,而不是遮蔽它。
  • Contains(name)——先查自身,再查父实例,Ordinal 比较。
  • SetClrTypeName(name, fullTypeName)——在一次仅有字符串的注册之后补上 CLR 名称;程序集名称保持为空,因为该类型此时还不存在。

"创建工作表"模板(SheetForge.Core.Model

类型角色与关键成员
DataTemplate一个由插件注册的模板:string Key(注册表身份标识) · string DisplayName(插件自有的文本) · IReadOnlyList<DataTemplateTab> Tabs(一个或多个)
DataTemplateTab模板中的一个标签页:string TabName · string Tsv(一份完整的规范化 TSV——标记行加上示例数据)

自定义单元格类型(SheetForge.Core.Model

类型角色与关键成员
ICellValueParser解析一个标量单元格。失败 = 收集进 context.Errors 并返回 false(绝不抛出异常)。string TypeName · bool TryParse(CellParseContext, string, out object)
ICustomCellType可选的代码生成/往返辅助接口。Type ValueType · bool TryRender(object, out string text, out string reason)
IReferencingCellType一个已注册的 ICellValueParser 可以选择性额外实现的capability,让藏在它自己记法内部的键获得完整的 RecordId@Tab 处理待遇——完整性校验 + 建议、保留载荷的键重命名传播、图边与端口、 选择器、孤立检测、导出的下拉规则。发现方式是对已注册的解析器做类型转换(不需要单独注册)。bool TryGetTokenKey(elementText, out key) · string MakeToken(key) · bool TryRetargetToken(elementText, newKey, out newText) · bool TryRemoveToken(elementText, key, out newText)(返回空结果 = 该元素消失)· bool TryRewriteKeys(elementText, IReadOnlyDictionary<string,string> renames, out newText)。一次调用对应一个元素(整个单元格,或按 ; 分隔的一个元素),因此载荷内不能包含 ;@target 必须指向一个真实的工作表标签页(否则报 UnknownTargetTab)。绝不抛出异常——false/null 表示"无法解读",重写操作会保留剩余部分
IRefBearingValue上面那个接口在取值一侧的另一半,由解析后的取值实现:IEnumerable<string> ReferencedKeys(声明顺序 = 诊断信息与建议预算所使用的顺序;null/空条目会被跳过)。扫描器读取的是这个接口;上面的文本钩子则负责重写单元格。两者缺一不可——解析后的取值无法还原作者的原始记法,而文本在被读取之前也无法被验证
ICellWrapperType一种通用的包装值形态 MyWrapper<T>(例如 Pair<int> = 1~2)——包装类型拥有外层语法,Core 递归解析内部类型。string Name · bool TrySplit(string, out IReadOnlyList<string> pieces, out string reason) · string JoinCanonical(IReadOnlyList<string>) · Type OpenClrType · object Assemble(IReadOnlyList<object>, Type closed) · bool TryDisassemble(object, out IReadOnlyList<object>, out string reason)
WrapperValue一个包装单元格解析后的 IR——携带包装策略,并暴露内部的 CellValue(因此其内部的引用会经过验证、键/标签页重命名、以及导出的透传处理)。ICellWrapperType Wrapper · IReadOnlyList<CellValue> Inner
IStructuralMarkerDefinition一个自定义的 @marker 行(按列取值,逐列验证——是 @overlap 的泛化)。string MarkerName(不带 @) · string Description · void ValidateCell(MarkerCellContext)
MarkerCellContext一次标记单元格验证调用。string MarkerName · string RawText · string FieldName · CellCoordinate Coordinate · void Reject(string reason, string suggestion = null)(→ MarkerCellInvalid
CellParseContext一次解析调用的上下文。TypeToken Type · CellCoordinate Coordinate · ErrorCollector Errors · EnumRegistry Enums

表格语法常量(SheetForge.Core.Model

读写单元格文本的插件包,遵循与导入器完全相同的语法——拆分列表单元格、组装 @type 字符串、检查某个名字是否已被占用。

这些常量就是该语法的唯一真相来源,所以一个插件包永远不需要自己重新写一遍分隔符:复制下来的字符会在语法变动的那天悄悄失效。这些列表都是只读地对外提供的,因此无论插件包做什么,都无法改动语法本身。它们所表达的记法在表格语法页面中有完整记录——这些常量正是通往该语法的编程接口。

类型种类角色
SheetSyntax静态类以常量形式呈现的工作表语法,按下方分组

标记

  • CommentPrefix#)· MarkerPrefix@)。
  • 每个内置标记行对应一个常量:NameMarker · TypeMarker · DescMarker · OverlapMarker · StyleMarker · EnumMarker · LocMarker
  • RequiredMarkers ——每张工作表都必须具备的那三个。
  • ReservedMarkers ——所有内置名称。为自定义标记命名之前先看看这份清单:冲突会在注册时被拒绝。

分隔符

  • ListSeparator;)——用在列表元素之间。
  • EntrySeparator,)、FieldSeparator:)、SectionSeparator|)、KeyTimeSeparator@)——这些是单个值内部的层级,这也是为什么 ; 绝不会出现在某个值自己的文本里。
  • StyleKeyValueSeparator=)——用在 @style 单元格内部。

@type 记法

  • OptionalSuffix?)· DefaultSeparator=)· TargetSeparator@,如 RecordId@Tab 中那样)。
  • ListTypeName · ListOpenList<)· ListClose>)。

类型名称

  • 每个内置名称对应一个常量:IntTypeName · FloatTypeName · BoolTypeName · StringTypeName · RecordIdTypeName · IntIdTypeName · AssetRefTypeName · LocRefTypeName · ColorTypeName · AnimationCurveTypeName · GradientTypeName · EnumTypeName
  • BuiltinScalarTypesIsBuiltinScalarTypeName(name)——"这个名字是不是已经是内置的了?",会在某个解析器以这个名字注册之前就先给出答案。
  • StyleKeyNamestitlecolor)· LocReservedColumnssmartcomment)。

  • TrueCanonical / FalseCanonical ——规范的 bool 文本。
  • NumberCellStyles ——读取每个数值单元格时所用的 NumberStyles。千位分隔符被排除在外,区域性(culture)始终是固定不变的(invariant),因此某个地区习惯的小数点逗号会直接、明显地报错,而不是悄悄地把数值改掉。

领域验证(SheetForge.Core.Validation

类型角色与关键成员
IDomainValidator跨列/跨标签页规则。违规 → 以 DomainRuleViolation 的形式进入 ctx.Errors,携带全部 4 个要素。string Name · Validate(DomainValidationContext)
DomainValidationContextTables(标签页 → SheetTable) · KeyIndices · AssetKeys(null = 已跳过) · Errors

边缝隙(SheetForge.Core.Validation / .Edges

类型角色与关键成员
ReferenceScanner(静态)枚举引用出现位置的唯一真实来源。Scan(tables) · ScanTable · ScanField · IsReferenceField(TypeToken),外加表格下方详述的两个引用判定函数
RefKeyKind(enum)一个引用匹配的是字符串(RecordId)键空间还是整数(IntId)键空间。由 ReferenceScanner.GetReferenceKind 返回;消费方据此分支处理。只追加,不移除
ReferenceOccurrence(结构体)一次出现——Kind · FromTab · RowNumber · ColumnNumber · FieldName · TargetTab · TargetId · ToCoordinate()
ReferenceOccurrenceKind(enum)Scalar · ListElement · ExplicitDefault · WrapperElement · CustomElement(一个由 IRefBearingValue 从自己记法中声明出来的引用——坐标精确到单元格级别,因为内部布局归那个类型所有)。只追加,不移除,因此已有取值的含义保持不变
IEdgeContributor声明扫描器看不到的边。不产生诊断信息。string Name · ContributeEdges(EdgeContributionContext, ICollection<EdgeSpec>)
EdgeSpec一条边——FromTab/FromRecordId/ToTab/ToRecordId(+ 可选的 FieldName、用于记录边的 PayloadTab/PayloadRecordIdLabel
EdgeContributionContext只读的 Tables + KeyIndices(没有错误收集器——边不是验证)
IAuthorableEdgeContributor一个 IEdgeContributor 可以选择性额外实现的capability,让它的边能够在图形画布上被编辑。bool TryPlanConnect(EdgeAuthoringContext, fromTab, fromRecordId, toTab, toRecordId, out EdgeCellWrite) · bool TryPlanDisconnect(EdgeAuthoringContext, RecordEdge, out EdgeCellWrite)——false = 不会暂存任何内容,对应操作会被禁用并给出原因;两者都在 try/catch 内部运行
IEdgeTokenEditor一个 IEdgeContributor 可以选择性额外实现的capability,让它的 token 中除键以外的剩余部分能够在连线检查器中被编辑。bool TryDescribeToken(EdgeAuthoringContext, RecordEdge, out EdgeTokenDescription) · bool TryPlanSetModifier(EdgeAuthoringContext, RecordEdge, string newModifier, out EdgeCellWrite)——两者读取的是同一个单元格(一条边知道自己指向哪里,但不知道它今天是怎么被拼写的),false = 该行被隐藏或被如实禁用;两者都在 try/catch 内部运行
EdgeTokenDescription一个 token 是什么,以及如何编辑它的剩余部分——TokenText(要高亮的片段)· ModifierText · HasModifier · ModifierLabel · IsChoice · Options / OptionLabelsnew EdgeTokenDescription(tokenText) = 没有剩余部分,因此不会绘制任何行;当选项列表为空时,选择型构造函数会回退为自由文本
IBatchAuthorableEdgeContributor一个可选启用的capability,是 IAuthorableEdgeContributor 的同级接口(不是继承关系):把连接/断开计划表示为单元格写入的列表,适用于一次手势必须同时更改多个成对单元格的数据。bool TryPlanConnectMany(EdgeAuthoringContext, fromTab, fromRecordId, toTab, toRecordId, out IReadOnlyList<EdgeCellWrite>) · bool TryPlanDisconnectMany(EdgeAuthoringContext, RecordEdge, out IReadOnlyList<EdgeCellWrite>)——整份列表会作为一步撤销操作暂存,要么全部生效,要么全都不生效;单数形式的贡献者依然可以正常工作(作为回退),当同一个类同时实现两者时,批量版本优先
IVirtualNodeFactory一个可选启用的capability:画布上"创建"手势所对应的不是一行新的工作表行。IReadOnlyList<VirtualNodeKind> KindsFor(EdgeAuthoringContext, tab, recordId)(每次构建菜单时都会调用——请保持轻量)· bool TryPlanCreate(EdgeAuthoringContext, tab, recordId, VirtualNodeKind, out IReadOnlyList<EdgeCellWrite>)——false = 会话不受影响。一个计划不能以在同一手势中刚创建的记录为目标
VirtualNodeKind(结构体)一种可创建的类型——Id(选中时原样返回) · Label(已经翻译好的菜单文本;/ 表示嵌套) · IsUsable。对 null 安全,对 default 安全
IEdgeSlotDeclarer一个可选启用的capability:一个(虚拟)节点无需一条实际存在的边就能开放的端口。IReadOnlyList<DeclaredSlot> DeclareSlots(EdgeAuthoringContext, nodeTab, nodeRecordId)——被声明的槽位会加入连接菜单、端口选择器和卡片的端口行;每次渲染都会调用,因此实现必须保持轻量且没有副作用
DeclaredSlot(结构体)一个被声明的槽位——FieldName(每个节点内唯一;必须与贡献者边的 FieldName 一致,连线才能锚定上去) · TargetTab · IsList · IsUsable。对 null 安全,对 default 安全
EdgeAuthoringContext规划所需的输入——Tables + string CellText(tab, recordId, field),返回的是该单元格此刻的实际读数(baseline 加上暂存内容),因此连续做出的两次链接彼此可见
EdgeCellWrite(结构体)这份计划本身:TabName · RecordId · FieldName · NewRawText(空 = 清除) · IsAddressable。按键寻址,而不是按行号

ReferenceScanner——两个引用判定函数:

  • GetReferencedTab(TypeToken)——每个消费方都会问的那个唯一判定:"这是不是一个引用,指向哪里"。它对 RecordId@TabIntId@Tab(整数键空间)、包装类型内部,以及被标记为 IsCustomReference 的自定义类型都能给出答案。这正是为什么一次选择启用——对于 IntId@Tab 而言,是对这个判定函数的一次扩展——就能把它们同时全部点亮。
  • GetReferenceKind(TypeToken)RefKeyKind——这个引用是对照字符串键空间还是整数键空间比较,从而让重命名传播、下拉菜单和选择器都能正确分支。
  • GetReferenceKind(TypeToken, tables)——感知表数据的重载版本。

一个核心引用会自己说明它属于哪个空间(RecordId / IntId)。而一个引用型自定义类型没有记法可以说明这一点——MyType@Tab 是唯一的写法——因此它的空间是从目标标签页自身的身份推导出来的:RecordId 自身键意味着字符串空间,单独的 IntId 意味着整数空间,一个未知的标签页或 null 的 tables 参数则回退为字符串空间——这与仅有 token 的那个重载给出的答案相同。正是这种推导方式,让一个既有的 IReferencingCellType 实现无需改动一行代码,就能指向一个以 IntId 为键的标签页。

引用图索引(SheetForge.Core.Edges

一份不可变快照,把核心扫描到的引用与贡献者提供的边合并进同一个模型,并做了双向索引。这是展示用的素材——它绝不会产生诊断信息(投影结果的 Diagnostics 依然是关于问题的唯一真实来源)。

类型种类角色与关键成员
RecordEdge(结构体)值类型一条边。RecordEdgeOrigin Origin · FromTab · FromRecordId(字段级别的边为空) · FieldName · RowNumber / ColumnNumber(从 1 开始;0 = 字段/标签页级别) · ToTab · ToRecordId(即便无法解析,也是其本意指向的 id) · bool IsDangling(在构建时就已确定) · Label · PayloadTab / PayloadRecordId(记录边)
RecordEdgeOrigin(enum)——CoreReference(读取自一个 RecordId@Tab 单元格——带有坐标) · Contributor(由一个 IEdgeContributor 声明——记录级别)
ReferenceIndexsealed 类这份快照本身。静态方法 Build(tables, keyIndices, contributorEdges, codeRegistries, extraKeys = null)(后三个参数可以为 null;extraKeys = 标签页 → 已经存在但尚未被解析的键,例如某个创作界面刚刚暂存的行,这样指向它们的链接就不会被画成损坏状态) · AllEdges(确定性顺序:来源标签页 Ordinal → 行 → 列 → 出现次序) · OutEdges(tab, recordId) / InEdges(tab, recordId)(绝不为 null) · int InCount(tab, recordId) · bool TryGetRowKey(tab, rowNumber, out recordId) · DanglingEdges

Data Studio 记录画布(SheetForge.Core.Graphing

画布会自行决定要画什么:它从你打开的记录(终点节点)出发,沿引用索引向外遍历,并以确定性的方式对结果进行布局。插件并不会替换这幅画面——它只会向其中追加内容。全程都是纯数据:列对应的是网格单元格,而不是像素,颜色则是一个自由的 Category 字符串,由窗口负责将其映射到调色板上。

类型种类角色与关键成员
IRecordCanvasAugmenter接口一个标签页的画布覆盖,在闭包组装完成之后才会被调用。Augment(GraphBuildContext, CanvasAugmentBuilder, string terminusTab, string terminusRecordId)。什么都不添加就会让核心画面保持原样;抛出的异常会被窗口捕获,变成一条英文的控制台警告。身份归属于数据(同一个键的真实记录会胜过虚拟节点);而表现层——即显示提示——则不受此约束
CanvasAugmentBuildersealed 类只提供四种写入能力的写入界面——成员与规则见表格下方
GraphShapeRegistrysealed 类标签页名称 → 画布覆盖。Register(tabName, IRecordCanvasAugmenter)(重复的标签页 / 空名称 / null 都会抛出异常) · TryGet · IsEmpty
GraphBuildContextsealed 类覆盖逻辑的只读输入。Tables(标签页 → SheetTable) · ReferenceIndex References · IReadOnlyList<CodeRegistrySource> CodeRegistries(可以为空集合,但绝不为 null)。没有错误收集器——画布是展示,不是验证
GraphSpecBuildersealed 类图形组装辅助工具。构造函数 (GraphBuildContext) · 静态方法 NodeKey(tab, recordId)(连线所指向的唯一真实标识) · AddNode(GraphNodeSpec)(相同 (Key, Column) 时以第一个为准) · AddWire(GraphWireSpec) · AddWire(fromKey, toKey, label, fromTab, fromRecordId, fieldName, isCyclic = false, cyclicNote = null)(这个重载同时指明了链接写入在哪个单元格中,这正是让连线可编辑的原因)
GraphSpecsealed 类画布用来绘制的组装结果——Nodes · Wires(组装过程要经过构建器;构造函数是 internal 的)
GraphNodeSpecsealed 类一个节点。Key · Tab · RecordId · Title · Subtitle · Category · CellCoordinate Address · Column / Row(画布已经解出的网格坐标——在这里是被携带的,而不是被选择的) · IsFocus(是否为终点节点) · IsMissing · InCount · IsCyclic
GraphWireSpecsealed 类一条连线。FromKey · ToKey · Label · IsCyclic · CyclicNote,外加可选的归属单元格:FromTab · FromRecordId · FieldName · RecordEdge? SourceEdge(null = 仅供显示的连线;此时画布会说明它无法被编辑)。这五个用于显示的参数保持不变,因此既有的调用依然能够编译,并且渲染效果完全一致
IAuthorableGraphShape接口一个 IRecordCanvasAugmenter 可以选择性额外实现的capabilityIReadOnlyList<string> CreatableTabs(GraphBuildContext, string tabName)——画布可以在哪里创建记录(空 = 哪里都不行)。不实现它时的两种默认行为见表格下方

CanvasAugmentBuilder——写入界面。 只有四件事:

  • AddNode(tab, recordId, title = null, category = null) / AddNode(tab, recordId, title, category, CellCoordinate address)——为一个不是工作表记录的身份(一个事件键、一个代码原子)添加一个虚拟节点;标签页可以为空。
  • AddEdge(fromTab, fromRecordId, toTab, toRecordId, label = null, fieldName = null, fieldOnTarget = false, isCyclic = false, cyclicNote = null)——添加一条核心扫描器看不到的额外边。指定 fieldName 说明这条链接写入在哪个单元格中,fieldOnTarget 说明那个单元格位于到达端而不是出发端,循环参数则用只有该领域才知道的说明来标记一个用于显示的循环。
  • SetLayer(tab, recordId, layer)——一个绝对层级提示(0 = 最左侧,负数 = 更靠左,其余内容会整体右移以补偿)。SetLayerRelative(tab, recordId, offset)——同样的效果,但以终点节点为基准计数(−1 = 它左侧一列),会先针对终点节点所在的列进行解析,然后任何提示才会移动它。
  • SetSubtitle(tab, recordId, subtitle)——一个显示提示,是唯一一个既适用于已经存在的记录、也适用于不在屏幕上的记录的设置(连接选择器会读取这些信息)。

键为空的条目会被忽略;已收集的内容是 internal 的,因为合并规则只存在于一个地方。每一次能力扩展都只新增末尾参数,因此针对更早版本界面编写的覆盖依然能够编译通过。

IAuthorableGraphShape——不实现它时的两种默认行为:

  • 这个 capability 所取代的可创建标签页列表——同一个轴也决定了画布是否会打开,以及待处理行的扫描范围能到多远——会通过沿架构传递地追踪,覆盖从焦点标签页可达的每一个标签页。
  • 而用户实际看到的连接级联菜单,则是从当前已画出的端口所指向的标签页开始的。

两者都会剔除代码注册表标签页和没有键列的标签页。这个方法返回、但当前没有任何已画出端口接受的标签页,会继续保留在连接级联菜单中并附上原因,窗口本身的关卡机制依然会在此之上生效。

色彩预设(SheetForge.Core.Theming

类型角色与关键成员
ThemeRegistry预设 id → 主题。空白 id、重复项,以及两个保留的内置 id,都会抛出异常。Register(SheetForgeTheme) · TryGet · Themes · IsEmpty · IsBuiltInId(id) · BuiltInDefaultId · BuiltInHighContrastId
SheetForgeTheme一个色彩预设。Id · DisplayName · DarkColors / LightColorsIReadOnlyDictionary<ThemeColorSlot, uint>,在构造时被复制) · TryGetColor(dark, slot, out rgb) · IsEmpty
ThemeColorSlotenum——一个预设可以覆盖的 33 个颜色角色(界面、线条、文本、语义色、暂存标记、失败状态界面、遮罩、图形)。颜色格式为 0xRRGGBB:Core 不引用任何引擎类型,半透明填充由某个插槽颜色加一个固定的透明度推算而来。只追加,不移除。

一个预设只会覆盖它指名的那些插槽;其余每一个插槽都保持产品默认值,因此即便后续新增了插槽,预设依然有效。注册这个动作本身绝不会应用某个预设——用户需要在 Preferences ▸ SheetForge ▸ Theme 中自行选择。

声明式创作界面(SheetForge.Core.Studio

一个插件只描述要展示什么——外壳以数据形式给出,判定条件和效果以委托形式给出——每个宿主再用自己的部件把它绘制出来:编辑器用 UIToolkit,浏览器用 React。任何地方都不出现布局数值。要说什么是插件的事,怎么摆放是渲染器的事。

这里的每一个 enum 都是只追加的,因此随着这套词汇不断扩展,一份已有的注册永远不会改变含义。

类型种类角色与关键成员
StudioUiRegistrysealed 类RegisterStudioUi 所填充的对象。AddAction(StudioActionDescriptor) · AddPanel(StudioPanelDescriptor) · AddColumnBadge(StudioColumnBadgeDescriptor) · AddCellEditorHint(StudioCellEditorHint) · Actions / Panels / ColumnBadges / CellEditorHints · IsEmpty
StudioUiNodesealed 类一个被描述出的片段,不可变,通过静态工厂方法构建——工厂方法、可读属性,以及 URL 规则见表格下方
StudioUiNodeKindenum上文提到的 13 种类型(RowLink
StudioActionDescriptorsealed 类一个动作。Id(唯一) · LabelKey(一个 Loc 键;未注册则原样显示) · StudioActionPlacement Placement · Func<StudioSurfaceContext,bool> AppliesTo · Action<StudioSurfaceContext> Execute · ConfirmKey(可选——宿主会先展示这句话)。宿主会在真正调用时重新检查 AppliesTo,因此一个过期的菜单项会得到一次如实的空操作加一次重绘
StudioActionPlacementenumInspector · RowContextMenu · TopbarMenu · ColumnHeaderMenu · CanvasNodeMenu。每个位置都会填充不同的上下文字段——行位置携带记录,列位置携带列名,画布位置携带该节点的记录
StudioPanelDescriptorsealed 类Studio 右侧窗格中的一个面板。Id · TitleKey · Func<StudioSurfaceContext,StudioUiNode> Build——每个重新计算节拍都会重建,因此不持有任何状态。没有面板被注册时,这块窗格根本不会被绘制
StudioColumnBadgeDescriptorsealed 类列表头旁的一个徽标。Func<StudioSurfaceContext,string,string,StudioUiNode> Provide(context、tab、field)——null 表示该列上不显示任何内容
StudioCellEditorHintsealed 类"为这个类型使用这个内置部件"——选择一种形态,而不是自己提供一个部件。TypeName(一个精确的 CellParserRegistry 类型名;列表单元格按其元素类型名匹配;包装类型单元格保留规范文本,永远不会被匹配) · StudioCellEditorArchetype Archetype · GetOptions(仅用于下拉菜单——Func<context, tab, field, IReadOnlyList<StudioCellOption>>) · SliderMin / SliderMax · ToggleTrueValue / ToggleFalseValue。四个构造函数,每种材质形态一个。会在一个已注册的 IStudioCellEditorProvider 放弃之、核心默认分支之被查询;一个插件包的 hint 会先于下方的内置 hint 被查询,因此在 ColorAnimationCurveGradient 下注册一个 hint,会覆盖该类型的默认编辑器。一个元素 hint 为 ColorPickerCurveEditorGradientEditorList<>,在两个宿主环境中都会变成一个纸片编辑器
StudioCellEditorArchetypeenumDropdown · MultilineText · Slider · Toggle · ColorPicker(单元格文本为 #RRGGBB / #RRGGBBAA) · CurveEditor(单元格文本 = 规范的 CurveValue 记法) · GradientEditor(单元格文本 = 规范的 GradientValue 记法)。只增不减——最新的两个是 56
BuiltinCellEditorHints静态类Core 自己声明的三个 hint——ColorColorPickerAnimationCurveCurveEditorGradientGradientEditor——走的是与插件包 hint 相同的路径,因此编辑器与浏览器不可能为它们选出不同的部件。IReadOnlyList<StudioCellEditorHint> All(固定顺序) · bool TryGet(typeName, out hint)(Ordinal)。宿主环境会先查询 StudioUiRegistry.CellEditorHints,查不到时再回退到这张表
StudioCellOptionsealed 类一个下拉候选项——Value(写入单元格的规范文本) · Label(供人阅读的文字;默认等于 Value
StudioSurfaceContextsealed 类一个扩展能够看到并据以行动的唯一接缝。读取:Tables · ReferenceIndex References · CodeRegistries · Tab · RecordId · Field · ActionArgument(一个 Input 节点提交的取值)。经过中介的变更,仅此而已:Action<string,string,string,string> StageCell · Action<IReadOnlyList<EdgeCellWrite>> StageCells(一步撤销,要么全部生效要么全不生效) · Action<string,string> FocusRecord · Action RequestRebuild。暂存会经过窗口自己的关卡,因此只读来源、正在运行的管线,或者以工作簿为来源的标签页,都会附带原因阻止它(构造函数是 internal 的:由宿主负责组装)

StudioUiNode——工厂方法、可读属性与 URL 规则:

  • 工厂方法: Row · Label · Chip · Badge · Button · Rule · Heading · KeyValue · Table(headerRow, rows) · List · Progress · Input · Link,外加 WithTooltip(text)——它返回的是一个节点,而不是修改当前节点。
  • 可读属性: Kind · Text · Tooltip · ThemeColorSlot? Tone(从不使用硬编码颜色,因此会跟随主题) · ActionId · Detail · Ratio · Url · Children
  • 静态方法 bool IsAllowedUrl(url)——仅限 http/https。这是两个宿主都会查询的同一个判定函数,因此它们对"打开什么是安全的"这件事永远不会产生分歧。

插件 UI 字符串(SheetForge.Core.Model

类型角色与关键成员
StringOverlayRegistry插件注册 UI 字符串的收集器查找覆盖层;Loc.Tr(编辑器)和 t()(浏览器)都会在查询产品语言表之前先查询它。Register(key, language, value) · Register(key, IReadOnlyDictionary<string,string> byLanguage) · bool TryGet(key, language, out value) · RegisteredKeys。语言匹配方式与四种拒绝情形见表格下方

StringOverlayRegistry——匹配方式与拒绝情形。 language 是一个 IETF 代码("en""ko""zh-Hans""pt-BR"……),匹配时不区分大小写。查找会依次回退——请求的语言 → 英语 → 未命中——这个回退逻辑就存在于这里,因此两个宿主给出的答案完全一致。

四种注册会被拒绝,每一种都会记录一条面向开发者的原因,而不是静默失败:

  • 一个产品内置键——一层覆盖可以新增键,但绝不能覆盖产品自己的句子或菜单路径;
  • 一个已经被另一个包注册过的键+语言组合——先发现的为准,否则安装顺序就会决定界面显示的内容;
  • 一个空的键或空的值;
  • 一个产品不认识的语言代码——它绝不会被折叠进英语。

管线观察(SheetForge.Core.Plugins / .Model

类型角色与关键成员
IPipelineObserver只读通知。void OnImportCompleted(PipelineRunView view)——每一次显式的导入周期只会在其结束时触发一次,无论成功或失败。这里刻意没有提供任何能修改取值或新增诊断信息的钩子(那些分别属于单元格类型和 IDomainValidator),也没有任何会在暂存预检上运行的钩子。一次抛出会被隔离,原因会被收集起来;导入的输出不会有任何改变。未来的观察节点将以同级 capability 接口的形式到来,通过对已注册的观察者做类型转换来发现,因此今天写好的实现会持续保持可编译
PipelineObserverRegistry只追加的观察者列表,保持顺序。Register(IPipelineObserver) · Observers
PipelineRunView一个观察者收到的不可变快照——Success(验证结果,即是否组装出了一个 registry;代码生成/烘焙的结果要从诊断信息中读取) · Tables(已解析的标签页;在一次失败的运行中,只有无法解析的标签页会缺席,因为"不允许部分组装"是一条输出规则,而不是一条观察规则) · Diagnostics(与报告展示的是同一份列表) · SkippedTabs · EnumTabs。各集合在构造时会被复制,构造函数是 internal 的,因此不会有半成品的快照被交给观察者

代码注册表(SheetForge.Core.Graphing

存在于代码中的引用目标,以锁定的虚拟标签页形式暴露给创作界面。它被 Data Studio(侧边栏 / 图形 / 检查器)消费,而不是被导入验证器消费。

类型种类角色与关键成员
CodeRegistryCatalogsealed 类注册根节点。Register(CodeRegistrySource)(null / 空标签页名称 / 重复的标签页名称都会抛出异常) · TryGet(tabName, out source) · Sources · IsEmpty
CodeRegistrySourcesealed 类一个锁定的虚拟标签页。string TabName · IReadOnlyList<CodeRegistryEntry> Entries(注册顺序 = 显示顺序)
CodeRegistryEntrysealed 类一个条目。string Key(一个引用可以指向的目标) · string Label · IReadOnlyList<string> Raises(null 会被归一化为空集合)。Core 把这三者都当作不透明的字符串处理

IR 读取模型(SheetForge.Core.Model

类型角色与关键成员
SheetTable一个标签页的解析输出。SheetSchema Schema · IReadOnlyList<SheetRecord> Records
SheetSchemastring TabName · Fields · TryGetField(name, out FieldSchema) · SheetStyle Style(该工作表的 @style 显示元数据) · bool IsLocalizationSheet(存在 @loc 标记) · IReadOnlyList<LocaleColumn> LocaleColumns(按原始列序排列的语言列——在并非本地化表格的工作表上为空,且永远不为 null) · TryGetLocaleColumn(localeCode, out LocaleColumn)(按代码查找,不区分大小写) · TryGetSourceLocale(out LocaleColumn)(第一个语言列;一个都没有时为 false
SheetStyle@style 行的取值——一张工作表的显示元数据。string Title(侧边栏分组标签) · string ColorHex(书写形式的 #RRGGBB) · bool HasColor · 静态成员 None(无样式)。代码生成、烘焙和架构指纹都不会读取它
LocaleColumn(结构体)一张本地化表格中的一个语言列——@loc 行在那一列上写下的内容。string Code(完全按书写形式呈现的代码;Core 校验的是拼写形态,而不是这个语言是否真实存在) · string FieldName · int ColumnNumber(从 1 开始) · bool IsSource(第一个语言列——内联预览读取的、以及自动铸造写入的那一列)
SheetRecordint RowNumber(原始行号,从 1 开始) · Values(字段 → CellValue) · TryGet · 索引器
FieldSchemaName · TypeToken Type · Description · ColumnNumber · DefaultValue · AllowOverlap · IReadOnlyDictionary<string,string> MarkerValues(自定义标记名称 → 该列的单元格文本)
TypeToken解析后的 @type 单元格。RawText · TypeName · TypeArgument · TargetName · IsList · IsOptional · HasExplicitDefault · DefaultValueText · AllowsEmptyCell · IsSelfKey · IsIntId(仅表示该标签页自身的整数键;IntId@Tab 这种引用形态要通过 TargetName + ReferenceScanner.GetReferencedTab 读取,与 RecordId@Tab 完全一样) · TypeToken InnerToken / IsWrapper(包装类型——递归的内部类型) · IsCustomReference(这一列是 MyType@Tab,其解析器实现了 IReferencingCellTypeReferenceScanner.GetReferencedTab 是读取它的唯一判定,这也是每个消费方无需改动签名就能一起获得支持的原因) · AssetTypeNameAssetRef@Group<Type> 中原样写出的 <Type>,不受限制时为 null;Core 只存储这个名字——解析它是 IAssetTypeResolver 的职责——列表或包装类型内部的 AssetRef token 上同样会打上这个标记)。构造函数末尾三个参数(innerTokenisCustomReferenceassetTypeName)都有默认值,因此既有的调用依然能够编译;此外更早期的 8 参数与 10 参数构造函数依然以重载的形式保留,因此已编译好的插件程序集无需重新编译就能继续工作
CellValue(结构体)一个带类型的单元格值;没有 null(IsDefaulted 标记具化后的默认值)。object Value · IsDefaulted · AsList · 静态方法 Of / Defaulted
RecordId(结构体)一个键值(Ordinal 相等性比较)。string Value · IsEmpty
RecordRefValue(结构体)一个 RecordId@Tab 单元格的值。TargetTab · Id
IntRefValue(结构体)一个 IntId@Tab 单元格的值——RecordRefValue 的整数键孪生形态。string TargetTab · int Id · bool IsEmpty · 静态方法 Empty(tab)(一个指向虚无的可选 IntId@Tab?
LocRefValue(结构体)一个 LocRef@Tab 单元格的值——RecordRefValue 的本地化孪生形态,之所以保留为一个独立类型,是为了让消费者仅凭这个值就知道它指向的是一张字符串表。string TargetTab · string Key · bool IsEmpty · 静态方法 Empty(tab) · ReferencedKeys。它实现了 IRefBearingValue,因此引用扫描器对待它的方式与对待一个核心引用完全相同
AssetRefValue(结构体)一个 AssetRef@Group 单元格的值。Group · Key(子资源的键形如 parent[sub]
EnumValue(结构体)一个 Enum<T> 单元格的值(字符串对——CLR 转换是烘焙阶段的工作)。EnumName · MemberName

类型化资源引用(SheetForge.Core.Model

AssetRef@Group<Type> 中的 <Type>宿主环境负责解析——Core 既不了解引擎,也不了解项目的程序集——Core 只对解析结果做判断。这里的一切都是纯数据。

类型种类角色与关键成员
IAssetTypeResolver接口AssetTypeResolution Resolve(string rawName)——输入一个名字,输出一个判定结果;同一个名字总是得到同一个答案(具体实现可以自行缓存)。它会被独立于 AssetKeyIndex 注入到 ImportPipeline 中,因此即便项目还没有 Addressables 设置,类型名依然会被解析;当没有注入任何解析器时(无头模式、浏览器),类型名相关的诊断信息就干脆不会产生。Editor 侧的实现会对照项目中已加载的 UnityEngine.Object 派生资源类型进行解析(没有白名单;组件和仅编辑器类型被排除在外)
AssetTypeResolutionsealed 类一个名字的判定结果——RawName · AssetTypeResolutionStatus Status · FullName(CLR 全名,嵌套类型用 + 连接;仅 ResolvedNotReferenceable 两种状态才有) · AssemblyName(生成的配套程序集必须引用的那个程序集——对程序集定义类型会被设置,对引擎模块和未解析的名字则为 null) · Candidates(永不为 null:有歧义时是候选项列表,未知名字时是最接近的匹配建议)。工厂方法 Resolved(raw, fullName, assemblyName) · Unknown(raw, suggestions) · Ambiguous(raw, candidates) · NotReferenceable(raw, fullName, assemblyName)
AssetTypeResolutionStatusenumResolved · Unknown(没有这个类型) · Ambiguous(短名称匹配了多个类型——请写出全名) · NotReferenceable(该类型位于像 Assembly-CSharp 这样的预定义程序集中,生成代码无法引用它)

代码生成会读取管线产出的这份已解析字典,对已解析的名字输出 AssetReferenceT<global::FullName>;一个在这份字典中找不到的名字绝不会被原样输出——该字段会回退为 AssetReference,并收集一条 AssetTypeUnresolvedFallback 警告。解析出的全名同样会被计入架构指纹。

视觉值类型(SheetForge.Core.Model

三种内置视觉类型不依赖引擎的值模型。每一个都是不可变的、实现了 IEquatable,并拥有自己的文本形式(TryParse / Render)——与表格语法页面所记录的是同一套记法——因此一个存储颜色、曲线或渐变的插件类型可以直接复用它们,而不必发明第二套记法。Editor 会把它们烘焙进 UnityEngine.Color / AnimationCurve / Gradient,并再读回来;浏览器则通过下方的取值器(evaluator)对它们取样,而不是重新实现这套数学运算。

类型种类角色与关键成员
ColorValuereadonly 结构体四个字节 R · G · B · A · 静态 Default#00000000) · 静态 TryParse(text, out value, out error)(接受 #RGB / #RGBA / #RRGGBB / #RRGGBBAA) · Render()(不透明时输出大写、六位数字的形式)
CurveValuesealed 类Keys(按时间升序) · PreWrap / PostWrap · 静态 Empty(没有关键帧——唯一没有文本形式的状态;Render() 会给出 "") · 静态 Create(keys, preWrap, postWrap)——唯一的构造路径:按时间排序、拒绝重复的时间点,并应用 CurveTangentSolver,使"模式优先"从一条曲线存在的那一刻起就成立 · 静态 TryParse(2/4/7/8 字段的关键帧,Once 会被当作 ClampForever 的别名接受,切线可以是 Infinity/-Infinity) · Render()(8 字段形式的关键帧,仅在需要时才带包裹后缀)
CurveKeyreadonly 结构体Time · Value · InTangent · OutTangent · InWeight · OutWeight · CurveWeightedMode WeightedMode · CurveTangentMode LeftMode / RightMode · bool Broken;一个十参数构造函数,本身不做任何归一化
CurveWrapenumClampForever · Loop · PingPong · Default——按名字对应 Unity 的包裹词汇(取值到 WrapMode 的映射属于烘焙器的职责)
CurveTangentModeenumFree = 0 · Auto = 1 · Linear = 2 · Constant = 3 · ClampedAuto = 4——名字和取值都与 AnimationUtility.TangentMode 完全一致,因此烘焙器按名字映射,从不触碰 Unity 打包好的切线位
CurveWeightedMode[Flags] enumNone = 0 · In = 1 · Out = 2 · Both = 3——一个关键帧的哪一侧使用加权(贝塞尔)切线
CurveTangentSolver静态类CurveKey[] Apply(IReadOnlyList<CurveKey> sortedKeys)——推导出某个模式所决定的切线数值,按引擎的顺序逐阶段处理(Linear 只处理自己一侧 → ClampedAuto 处理两侧 → Auto 处理两侧 → Constant 只处理自己一侧),Free 一侧与权重则保持不变。CurveValue.Create 会调用它,因此调用方很少需要亲自调用
CurveEvaluator静态类float Evaluate(CurveValue, float time) · float[] Sample(CurveValue, int count)count ≥ 2,从第一个关键帧到最后一个关键帧均匀取样)——关键帧之间用 Hermite 插值,权重标志被设置的一侧用加权贝塞尔,切线为无穷大时保持不变(hold),关键帧范围之外则是四种包裹行为;已通过对随机曲线与 AnimationCurve.Evaluate 的比对验证
GradientValuesealed 类ColorKeys · AlphaKeys(各 1 到 8 个,按时间升序) · GradientBlend Mode · GradientColorSpace ColorSpace · 静态 Default(白色、完全不透明、Blend) · 静态 Create(colorKeys, alphaKeys, mode, colorSpace)(校验数量与 0…1 范围,像 Unity 一样把时间量化为 16 位,并进行稳定排序) · 静态 TryParse(三或四个以 `
GradientColorKeyreadonly 结构体ColorValue Color(alpha 会被忽略——alpha 有自己的关键帧) · float Time
GradientAlphaKeyreadonly 结构体float Alpha · float Time
GradientBlendenumBlend · Fixed · PerceptualBlend
GradientColorSpaceenumUninitialized(不会被写出;读取时视为 Gamma) · Gamma · Linear——只有 PerceptualBlend 会受影响
GradientEvaluator静态类ColorValue Evaluate(GradientValue, float time) · ColorValue[] Sample(GradientValue, int count)——线性、阶梯或感知(Oklab)混合,alpha 关键帧单独混合,最终舍入为字节;已通过对随机渐变与 Gradient.Evaluate 的比对验证

错误与结果(SheetForge.Core.Model / .Reporting

类型角色与关键成员
ImportError结构化的、与语言环境无关的错误。Code · Severity · Coordinate · ActualValue · Expected · Suggestion
ImportErrorCode(enum,105)完整的"为什么"目录——它所覆盖的类别见表格下方。只追加,不移除,因为渲染器的映射表是以成员取值为键的
ImportSeverity(enum)Error(阻止输出) · Warning
CellCoordinate(结构体)标签页 · 从 1 开始的行号 · 从 1 开始的列号 · 字段;计算出对应的表格列字母。ForTab / ForRow 工厂方法
ErrorCollector收集一切信息的汇总器。All · HasErrors · ErrorCount · Add
ImportResult管线的输出。不变量:Success == false ⇔ Registry == nullSuccess · Registry · Diagnostics · SkippedTabs · EnumTabs(被当作枚举定义表读取的标签页,因此永远不会被解析为数据表——与 SkippedTabs 分开存放,因为后者的含义是"尚无表格可写",这样报告中的跳过计数才能保持真实;两者都是保留集合,会为这些标签页保留已生成的代码、烘焙资源和地址) · 静态方法 Succeeded / Failed
ImportReport.Reporting,程序集 SheetForge.Core.Tooling报告渲染器的输入——Timestamp · SourceDescription · TabCount · RecordCount · Success · Diagnostics · ErrorCount · WarningCount · SkippedTabCountTabCount 中有多少是被跳过的空工作表,而不是被真正导入的——报告头部会把它打印出来,避免让人把标签页总数误当成"全部已导入")
ImportReportText.Reporting,程序集 SheetForge.Core.Tooling,静态)把一份报告渲染成产品自己的人类可读字符串,不会向控制台写入任何内容,也不会附加跳转链接或机器可读的坐标行(那些属于控制台自己的约定)。string Render(ImportReport report, IReadOnlyDictionary<string,string> languageTable = null, string operationName = null)——省略语言表即为英文;省略操作名称时,会从同一张语言表中读取,因此这句话永远不会混用两种语言。编辑器侧的调用方通常想要的是 SheetForgeActions.RenderReportText(report),它会自动填入当前的编辑器语言(一个纯粹的程序集无法读取 EditorPrefs

ImportErrorCode——它所覆盖的类别:

  • 标记、架构、类型、单元格、键/引用与资源键;
  • 来源/文件、csv/xlsx、代码生成标识符、addressables、baseline/导出、Google/认证/推送,以及模板;
  • 插件——PluginRegistrationConflict,外加当一个程序集的兼容性声明超出这个宿主的可读范围时的 PluginIncompatible
  • IntId——DuplicateIntId,以及针对 IntId@Tab 引用的 UnresolvedIntId · TargetTabHasNoIntId
  • @overlapDomainRuleViolation
  • 枚举定义表——EnumSheetMarkerConflict · DuplicateEnumName · EnumSheetEmptyColumn · InvalidEnumIdentifier · InvalidEnumUnderlyingType · InvalidEnumMemberValue
  • DropdownNotSupportedByFormat,它是一个警告而不是错误;
  • 类型化资源引用——UnknownAssetType · AmbiguousAssetType · AssetTypeNotReferenceable(每列在 @type 行上报告一次)、逐个单元格报告的 AssetTypeMismatch,以及代码生成阶段的警告 AssetTypeUnresolvedFallback

索引与实用工具(SheetForge.Core.Validation / .Model / .Parsing / .Unparse

类型角色与关键成员
TabKeyIndex一个标签页的键信息——字符串键列,加上该标签页的 IntId 整数键集合,因此 RecordId@TabIntId@Tab 两种引用都能对照它解析。TabName · KeyField · HasKeyColumn · Keys · Contains(id)
KeyIndexBuilder(静态)一次性构建键索引(字符串键与 IntId 整数键集合),报告键相关错误,验证 IntId 列。Build(SheetTable, ErrorCollector) · ValidateIntIdColumns
AssetKeyIndex组 → 合法键集合(由 Editor 从 Addressables 目录中填充,包含子资源键;注入 null 表示跳过资源验证)。Register(group, keys) · HasGroup · HasKey · KeysOf · GroupNames,外加 AssetRef@Group<Type> 所使用的类型层:RegisterTyped(group, key, satisfiedTypeFullNames)(这个键,外加它能够被加载成的全部类型全名的闭包——它自己的类型、基类、接口,以及它子资源的类型;重复注册会对这个闭包取并集) · HasTypeInfo(group, key) · SatisfiesType(group, key, typeFullName)。一个仅通过普通 Register 注册的键没有闭包,会被豁免类型检查,而不是检查失败
LocalizationCoverage(静态)一张本地化表格按语言统计的覆盖率与孤儿键。这是一次纯粹的计算,它返回列表而不是收集错误,因为一个未翻译的单元格和一个没人使用的键都是正常状态,而不是需要拦下的出口。IReadOnlyList<LocaleCoverage> Compute(SheetTable) · IReadOnlyList<string> FindOrphanKeys(locTabName, tables)(没有任何东西指向的键;这里刻意保守——扫描器认识的每一种引用形式都算作一次使用,因此一条仍在使用的翻译永远不会被称作孤儿)
LocaleCoverage(sealed 类)一种语言的覆盖率。LocaleColumn Locale · int TotalKeys · int TranslatedKeys · IReadOnlyList<string> MissingKeys(按工作表的行序排列,永远不为 null) · bool IsComplete
TextSuggestion(静态)最接近匹配建议(有界的 Levenshtein 距离,结果确定)。FindNearest · Distance · DistanceWithin
BuiltinCellParsers(静态)CreateDefaultRegistry()——12 个内置解析器(intfloatboolstringEnumRecordIdAssetRefIntIdLocRefColorAnimationCurveGradient)。
CanonicalValueRenderer(静态)值 → 规范单元格字符串(导出/推送)。TryRender(…)(把 ColorValue / CurveValue / GradientValue 委托给它们各自的 Render();没有关键帧的曲线会渲染为空单元格) · RenderFloat(float)(最短可往返格式)

推送计划(SheetForge.Core.Unparse

之所以公开,是因为 IPushApprover.Approve(PushPlan) 会暴露它们;纯数据。

类型角色
PushPlan(程序集 SheetForge.Core.Tooling,以下三行同属该程序集)整个发送计划。Tabs · HasWork
PushTabPlan一个标签页:Writes · Appends · Deletes(键 + 行号;DeleteNotices 仍保留为仅含键的视图)
PlannedCellWrite一次单元格写入——坐标、baseline 单元格、新值/文本、字符串族标志
PlannedRowAppend一次追加行——完整的单元格文本 + 字符串族列

Editor 程序集(SheetForge.Editor

设置、本地化与组合(SheetForge.Editor.Pipeline / .Localization

类型角色与关键成员
SheetForgeSettings(SO)设置资源。字段:sourceProviderId(唯一的来源选择轴;为空 = 内置的 LocalFile) · localFolderPath · bakeOutputFolder · generatedCodeFolder · generatedNamespace · exportFolderPath · exportFormat · spreadsheetId · googleAccessMode · serviceAccountKeyPath · gidMapGidMapEntry { tabName, gid } 的列表)。已解析的 Effective* 属性。
Loc(静态)本地化入口点。Tr(key) · TrContent(…) · Table · MenuRoot 常量。Tr 按四个步骤解析:插件注册的字符串(先当前语言,再英语——这一层回退归覆盖层自己所有,见 StringOverlayRegistry)→ 内置语言表(先当前语言,再英语)→ 键本身。插件字符串只有唯一一条注册通道,因此"哪一次注册胜出"永远不会成为一个问题
PluginRegistry(静态)通过 TypeCache 发现插件,再把候选类型交给 PluginComposition.ComposeBuild · BuildValidators · BuildEdgeContributors · BuildStructuralMarkers · BuildTemplates · BuildGraphShapes · BuildCodeRegistries · BuildThemes · BuildAll(组合包) · InvalidateCache()(丢弃这份存活于整个重载期间的缓存——与 SourceProviderRegistry.InvalidateCache 的约定相同;它同时也会丢弃兼容性关卡自己的缓存,因此一份变化了的发现集合会被重新判定)。组合包与槽位隔离机制见表格下方
ImportEvents(静态)编辑器侧的事件总线——一个公共契约:外部资产可以订阅它。event Action<ImportCompletedArgs> ImportCompleted · RaiseImportCompleted(ImportCompletedArgs) 只会在一次导入一路跑完烘焙时触发,此时订阅者可以读取烘焙出的资源。event Action<BaselineUpdatedArgs> BaselineUpdated · RaiseBaselineUpdated(BaselineUpdatedArgs) 会在每一次工作表快照被保存时触发——包括一次验证失败的运行——这正是一个创作界面在一次隔离导入之后用来刷新自己的方式。两条轴刻意没有合并:一条表示"工作表变了",另一条表示"资源变了"
BaselineUpdatedArgs(sealed)baseline 保存事件的负载。IReadOnlyList<string> Tabs(被写入快照的标签页) · bool Quarantined(这次刚保存的快照是否验证失败)
SheetForgeActions(静态)运行门面——与菜单点击所触发的完全相同的一套流程,可以从一个 CI 脚本、一个构建钩子,或者你自己的按钮中调用。RunImport() · RunExport() · RunPush() · RunHealthCheck() · RunLocalizationSync()(每一个都是委托调用;设置解析、Addressables 关卡、互斥控制、确认弹窗、进度条,以及代码生成→编译→烘焙的续接,全都留在产品内部处理) · bool IsBusy · bool TryBeginExclusiveScope(out IDisposable scope)(当已经有其他操作在运行时返回 falsescope = null;这个 scope 就是用来释放的对象,第二次 Dispose 无法释放别人的运行) · string RenderReportText(ImportReport)(产品自己的句子,使用当前编辑器语言,不写入控制台)。完成语义见表格下方
SheetForgeEditorInfo(静态,命名空间 SheetForge.EditorEditor 程序集的锚点——const Version,是 SheetForgeRuntimeInfo 在编辑器侧表面用于功能门控的对应版本
ImportCompletedArgs(sealed)传给订阅者的完成事件负载。IReadOnlyList<string> Tabs(本次完成所烘焙的标签页) · string BakeFolder(Database SO 所在文件夹)。采用参数对象模式——未来新增字段不会破坏事件签名。
GoogleSheetAccessMode(enum)SheetsApi(认证,可写) · ExportUrl(无需认证,只读
ExportFormat(enum)Tsv · Csv · Xlsx · Json · MatchSource

PluginRegistry——组合包与槽位隔离。 内嵌的 PluginBundle 暴露的是组装完成的 PluginSet Set——那个十二槽位的唯一真实来源,这也是一个新增槽位无需拓宽这个组合包本身就能被读取的原因——外加九个指向它的便捷窗口:Enums · Parsers · Validators · EdgeContributors · Markers · Templates · GraphShapes · CodeRegistries · Themes。更早期的六参数和八参数构造函数依然以重载的形式保留,会把后续新增的注册表默认设为空,其行为与这些契约存在之前的版本完全一致。

隔离机制属于 Core,而不属于这个类型本身:一个在注册时抛出异常的插件会被点名警告并跳过,其余每一个槽位——以及其他每一个插件——依然会正常完成注册。

SheetForgeActions——完成语义。 RunImport/RunPush 是即发即忘的——它们的方法体是 async void,因为编辑器主线程无法阻塞等待网络 IO,所以它们的返回并不代表完成:请订阅 ImportEvents.ImportCompleted 来获知完成状态。RunExport/RunHealthCheck/RunLocalizationSync 是同步完成的——RunLocalizationSync 走的正是一次导入完成时所走的工作表 → StringTable 路径,而在没有 Unity Localization 包时,它会显示安装提示,并且什么都不改。

来源提供方缝隙(SheetForge.Editor.Sources

类型角色与关键成员
ISheetSourceProvider提供方契约。Id · DisplayNameKey · CreateTabSource(settings) · GetVisibility(settings) · CanAuthor(settings) · CreateReflectTarget(dispatcher, settings)
ISourceReflectTarget写回目标。void Reflect()
SourceVisibility应该显示哪些设置字段——5 个布尔标志
SourceProviderRegistry(静态)发现/解析。All · ResolveActive(SheetForgeSettings) 以及 ResolveActive(string providerId)(直接从一个 id 解析,无需手头有一个设置资源) · TryGet · InvalidateCache
ITabSource获取抽象。Description · Task<TabSourceResult> FetchAsync()
TabSourceResult各标签页(名称 → 原始 TSV) + 诊断信息 + 按标签页的格式;允许部分输出。静态方法 Create
TabSourceFormat(enum)Tsv · Csv · Xlsx · GoogleSheet

Data Studio 扩展点(SheetForge.Editor.Studio

之所以位于 Editor 侧,是因为它们会返回 UIElements 或涉及窗口状态——这与 ISheetSourceProvider 所具有的正当不对称性相同。全部四个契约都由 TypeCache 发现(无参构造函数;无需注册调用),并且全都在 try/catch 内部被调用。窗口本身(DataStudioWindow)是 internal 的。

任何能以数据表达的内容都应该改用 Core 的 ISheetForgeStudioPlugin 词汇,那样同样会在浏览器中渲染。这里介绍的是描述性词汇说不清楚时才用得上、没有上限的逃生舱。

最后四个条目并不是契约,而是一个已挂载部件可以使用的工具:

  • 窗口自己的只读皮肤取值,让它看起来像是属于这个窗口的一部分;
  • 键下拉菜单,让一个单元格部件能以内置单元格相同的方式选取键;
  • 以及发现缓存重置,让你自己的测试能够重新发现一个探针。
类型种类角色与关键成员
IStudioGraphWidget接口图形画布上方的一条领域信息条(核心不附带任何实现)。bool AppliesTo(StudioGraphContext) · VisualElement Create(StudioGraphContext)(每次图形重建都会重新创建——不要持有任何状态;null 表示不添加任何内容)
StudioGraphContextsealed 类只读:TabFocusRecordId(终点节点) · SheetRecord FocusRecord(无法解析时为 null) · Tables · ReferenceIndex References · CodeRegistries。有两个已淘汰的轴出于签名兼容性而保留,并标记为 [Obsolete]ShapeId(始终为 "record")和 ModeId(始终为空)。对它们中任意一个做比较都能编译通过,但结果永远不为真,因此编译器现在会直接指出这一点,而不是留下一个死分支——请删除这个判断。没有暂存能力——部件只能用于显示(构造函数是 internal 的:由窗口负责组装)
IStudioCellEditorProvider接口为一个具名类型绘制一个网格单元格。string TypeName(匹配一个 CellParserRegistry 类型名或包装类型名,Ordinal 比较;空字符串表示不参与) · VisualElement CreateEditor(StudioCellEditorContext)——返回 null 表示放弃该单元格,改由内置部件接管。同一个类型名被重复认领时会发出警告,并保留最先发现的那个
StudioCellEditorContextsealed 类单元格部件所获得的内容:Tab · FieldName · TypeToken Type · CurrentRawText(已应用暂存内容的规范文本) · Action<string> Commit(一次性提交动作——拥有自己的一步撤销) · Action<string> CommitTyping(一连串按键——按单元格合并) · Func<string,IReadOnlyList<string>> ReferenceKeys(与内置选择器提供的相同候选键集合)。两种提交方式都会经过窗口的暂存关卡(构造函数是 internal 的:由窗口负责组装)
IStudioInspectorAction接口节点检查器上的一个额外按钮。string LabelKey(Loc 键;未注册 = 原样显示,空 = 类型名) · bool AppliesTo(StudioInspectorContext) · void Execute(StudioInspectorContext)
StudioInspectorContextsealed 类读取:Tab · RecordId · SheetRecord Record · Tables · References · CodeRegistries。经过中介的变更操作:Action<string,string,string,string> StageCell · Action<IReadOnlyList<EdgeCellWrite>> StageCells,两者的详情见表格下方。服务:Action<string,int,string> FocusCell · Action RequestRebuildAuthoringSession 被刻意暴露出来
IStudioPanelProvider接口Studio 右侧窗格中一个任意的 UIToolkit 面板——描述性的 StudioPanelDescriptor 之外的逃生舱。string Id · string TitleKey · bool AppliesTo(StudioSurfaceContext) · VisualElement CreatePanel(StudioSurfaceContext)null 表示这一节拍不绘制任何内容)。用同一个 Id 注册一个描述性面板,每个宿主就会取用自己能绘制的那一份:编辑器优先使用这一个,浏览器绘制描述性的那一个——因此"浏览器能做到多少就是多少,编辑器里则一路做到底"无需第二套契约。这个元素只存活一个重新计算节拍,因此它不持有任何状态
StudioPalette静态类窗口自己用来绘制界面的只读颜色、间距与字号数值,好让你挂载的部件与窗口保持一致,而不必硬编码十六进制颜色值。每个插槽都在读取时才解析,因此部件可以免费跟随明暗模式与色彩预设一起变化。选择具体取值(预设、明暗、默认值)依然是 internal 的——部件只能跟随调色板,不能重新为它上色。成员列表见表格下方
StudioTheme静态类只有四个成员:CategoryColor(category)(与窗口为该分类赋予的相同确定性色调) · Np(text)(安全地插值进一段富文本标签中) · Mono / ApplyMono(element)(等宽字体策略:仅用于键、地址和数字——等宽字体没有 CJK 字形)。这个类型上的其余一切都是 internal 的
StudioKeyPicker静态类只有一个成员:Show(Rect screenAnchor, string targetTab, IReadOnlyList<string> candidates, Action<string> picked, string acceptsLabel = null)——与内置引用单元格所打开的相同下拉菜单,供需要在自己记法内部选取一个键的单元格部件使用。它会从你提供的候选项中选取一个键并交还给你;创建记录、把单元格留空、对列表做多选切换,以及询问由哪个端口接收所选内容,这些都是内置引用单元格自己的规则,因此不在这个外观接口的范围内。picked 是必需的(在创建任何窗口之前就会抛出 ArgumentNullException);没有候选项、也没有其他可提供内容时,它会记录日志,而不是打开一个空列表。窗口类型本身依然保持 internal
StudioPluginRegistry静态类只有一个公共成员:InvalidateCache()——丢弃这份按重载周期缓存的发现结果,好让你的测试刚刚启用的一个探针能够被重新发现(这与 PluginRegistrySourceProviderRegistry 已经提供的礼遇相同;这一个此前是唯一的例外)。被发现的列表本身依然保持 internal:外部无法读取或替换窗口即将挂载的内容

StudioInspectorContext——两个经过中介的暂存委托:

  • StageCell 接受标签页、记录 id、字段和规范原始文本。窗口会负责注册撤销步骤、递增投影代次,并暂存这个逻辑地址。
  • StageCells多个必须一起变更的单元格执行同样的操作:一步原生撤销,要么全部生效要么全都不生效。只要其中一个无法暂存,整个会话就完全不受影响。

无论如何,失败在画面上都是静默的,只有关卡本身会自我说明:只读来源、已经在运行的管线,或一个由工作簿支撑的标签页,会把原因写入控制台;而空列表、缺失标签页或字段的写入,以及解析不到任何行的记录键,则什么都不做,也什么都不说。

StudioPalette——成员列表:

  • 33 个颜色插槽:Canvas · Panel · Band · Chrome · Surface · Chip · Selection · PendingCell · Line · LineSoft · GridLine · LineHover · Text · TextMuted · TextFaint · RefText · OnAccent · Accent · AccentDim · Warning · Danger · Ok · SheetTone · CodeTone · EditedCell · NewRowCell · NewRowLine · DangerChip · DangerPanel · Scrim · Wire · WireDot · GridDot
  • IsDark
  • 间距:SectionSpace · RowSpace · RuleHeight · ButtonHeight · PrimaryButtonHeight · GlyphWidth
  • 字号:HeadingFontSize · SectionFontSize · CaptionFontSize
  • FromRgb(uint) · ToHex(uint)

推送批准(SheetForge.Editor.Push

类型角色
IPushApproverbool Approve(PushPlan, string humanSummary) · bool ApproveStructureRewrite(string, bool hasLiveConflicts)——拒绝 = 不发送
AutoPushApprover总是批准(用于测试/自动化)

创作引擎(SheetForge.Editor.Structure / .Pipeline / .Export

类型角色与关键成员
AuthoringSession暂存状态的持有者(可序列化——免费获得撤销 + 重载存活能力)。Edits · IsolatedEdits · NewRows · StructOps · Reorders · TabRenames · EnumMembers(暂存的枚举表新增成员) · AssetRegistrations(暂存的 Addressables 注册——项目级别,因此不参与逐标签页关卡,但会计入反映入口、丢弃与差异摘要) · HasAssetRegistrations · StageAssetRegistration(r)(同一个 guid,或用于创建组时的同一个组,会原地替换——以最后一次意图为准;一项没有身份标识的注册会被拒绝) · RemoveAssetRegistrationsWhere(predicate) · SetStaged · ResolveBaselineEdits · RemapFieldName/RecordId/Tab · StageTabRename · EffectiveStructOps · PendingStructCount · TabNames · TryGetBaselineTable · LastProjectionResult · ClearAll(同时清空这些注册)
AuthoringDispatcher反映编排器。构造函数 (session, callbacks, baselines) · Reflect() · BuildProjectionResult()(无副作用的投影查询) · IReadOnlyDictionary<string,string> BuildProjectedTabs()(与上面相同的投影,但以每个标签页一份 TSV 的形式给出——即一个写回目标即将发送的内容,无需真正写入即可预览) · void FinalizeReflectSuccess(IReadOnlyList<string> writtenTabs, IReadOnlyList<TabRenameEntry> committedRenames = null)(一个来源自己的写回逻辑必须抵达的终点:为它所写入的标签页做保留性清理、ClearUndo 边界,以及自动重新导入——内置路径运行的是同一段私有逻辑,因此一个外部提供方的结束方式与它们完全一致;传入空列表是一次空操作,暂存内容保持不变) · Session · Callbacks · Baselines
AuthoringDispatchCallbacks13 个通用的视图相关委托 + IPushApprover——ResolveSettings · RenderReportAction<ImportReport>,可为 null) · TriggerReimport · ConfirmKeyRenames · ConfirmTabRenames(可为 null) · ClearUndo · Rebuild · … 内置的 Local/Google 对话框委托存放在可选的 BuiltInSourceDialogs 组合包中
BuiltInSourceDialogs一个可选的组合包,内含 14 个内置 Local/Google 来源对话框委托,与 AuthoringDispatchCallbacks 分离——外部提供方从不需要它们。NotifyLocalDone 接受五个参数;最后一个是提供给完成对话框的 Addressables 注册汇总行(没有暂存任何内容时为 null
BaselineStore.Export按标签页归一化的 TSV baseline 快照

暂存值类型(SheetForge.Editor.StructureStagedCellEdit/StagedNewRow 位于 SheetForge.Editor.Windows

类型角色
StagedCellEdit(结构体)一次暂存编辑——TabName · RowOrdinal · FieldName · RawText · RecordId(逻辑键)
StagedNewRow一条暂存新行——TabName · FieldNames · CellTexts
StructureOp一次结构操作——Kind · 坐标 · 文本 · Order 排列
StructureOpKind(enum)AddColumn · RemoveColumn · AddMarker · RemoveMarker · RemoveDataRow · ReorderColumns · ReorderDataRows · RenameColumn · EditColumnType · EditColumnDesc · SetColumnOverlap · SetSheetStyle
TabReorderEntry按标签页的重排序状态——Tab · ColOrder · RowOrder
TabRenameEntry(结构体)OldName · NewName
StagedEnumMember(结构体)一次暂存的"把这个成员加进这个枚举"操作——TabName(哪一张枚举定义表;空 = 在所有表中搜索) · EnumName · Member。它是会话级别的,而不是一个 StructureOp,原因与标签页重命名相同:一张枚举定义表没有表格、没有架构、也没有键列,因此一次单元格编辑的 (标签页, 记录, 字段) 地址无法表达"这个枚举的下一个成员"。之所以是公共的,仅仅是因为 AuthoringSession.EnumMembers 是公共的(CS0050
StagedAssetRegistration(结构体)一次暂存的、对项目 Addressables 设置的更改,由把一个资源拖放或选取进一个 AssetRef@Group 单元格而产生——StagedAssetRegistrationKind Kind · Guid(该资源;子资源会暂存它的父项) · Group · FromGroup(仅移动时使用) · Address(新条目为不带扩展名的文件名;已注册的资源保留原有地址) · AssetPath(用于展示)。工厂方法 Add(guid, group, address, assetPath) · Move(guid, fromGroup, group, address, assetPath) · CreateGroup(group)。在工作表写入成功后执行,随后被清空。之所以是公共的,仅仅是因为 AuthoringSession.AssetRegistrations 是公共的(CS0050),与 StagedEnumMember 同理
StagedAssetRegistrationKind(enum)Add · Move · CreateGroup
TabBaselineAnchor(结构体)TabName · Fingerprint · RecordCount
IsolatedEdit一次重新锚定失败的编辑——Edit · Reason
IsolationReason(enum)外部重命名 / 外部删除 / 键冲突

创作辅助工具(SheetForge.Editor.Windows / .Structure

类型角色
KeyRenamePlanner(静态)键重命名 + 跨标签页传播的规划。Plan(…) · 内嵌类型 KeyRenamePlan · 同级结构体 KeyRename
RecordIdMinter(静态,纯函数)id 建议。Suggest · DetectCommonPrefix · Uniquify · StagedNewRowKeys
IntIdMinter(静态,纯函数)为一条新记录建议下一个 IntId——Suggest(existingIds)max + 1。这是与 RecordIdMinter 分开的一条独立轴线,并且它永远不会复用一个已被删除的空缺号
ProjectionErrorMapper(静态,纯函数)错误坐标 → 逻辑地址。TryMap(…) · 内嵌类型 LogicalAddress
EphemeralSoApply(静态)暂存取值的 SO 叠加层(临时)。Apply(…) · InvalidateIndex(…) · 内嵌类型 Report / SkipReason / SkippedEdit

Runtime 程序集(SheetForge.Runtime

autoReferenced——游戏代码无需 asmdef 引用即可使用。

类型角色与关键成员
SheetForgeDatabases(静态)运行时加载器——官方认可的加载路径。const AddressPrefix = "SheetForge/" · AddressFor(tab) · LoadAsync(tab) · LoadAsync<TDatabase>(tab) · Release(handle) / Release<TDatabase>(db)。地址相关的辅助方法只是普通字符串处理,永远能编译;LoadAsyncRelease 只在 SHEETFORGE_ADDRESSABLES 之下才存在,这是 com.unity.addressables 安装后才会设置的版本 define——正是这一点让产品在没有该包的情况下也能编译
DefinitionDatabase(抽象 SO)每个生成的按标签页 Database 的基类。abstract TabName · abstract Count · virtual IReadOnlyList<object> RecordsUntyped · virtual InvalidateIndex()RecordsUntyped在不知道其生成类型的情况下枚举一个已烘焙标签页的官方认可方式——过去,一个二次烘焙工具或者一个需要遍历每个标签页的检查器,不得不通过反射去读取私有的 records 字段,这就把一个字段名变成了一份未声明的契约,一旦代码生成把它改名,就会静默地崩溃。请把这份列表当作只读的(工作表才具有权威性)。它默认是空的,因此在这个成员存在之前生成的代码依然能编译、能运行;重新导入会生成对应的覆盖实现
RecordRef(结构体)烘焙出的 SO 内部所序列化的引用值(字符串 id,在查找时解析)。Id · IsEmpty
IntRef(结构体)烘焙出的 SO 内部所序列化的整数键引用值——RecordRefIntId@Tab 字段上的孪生形态。由于 0 是一个合法的 id,IsEmpty 由一个 hasValue 位支撑。Id · IsEmpty。代码生成会把一个 IntId@Tab 字段生成为 IntRef,生成的 Database 上的 TryGet(IntRef) 会消费它
LocRef(结构体)烘焙出的 SO 内部所序列化的本地化引用——一个 LocRef@Tab 单元格。Table(本地化标签页,也就是 StringTable 集合的名称) · Key · long KeyId0 表示"尚未解析":一次导入烘焙出的是 0,桥接会在一次表格同步之后填入真正的 id,因此一个引用能在键被重命名后存活下来) · IsEmpty。它始终可以编译——生成的代码与烘焙出的资产从不包含任何本地化包的类型,正是这一点让这个包保持可选
LocRefExtensions(静态)只有一个成员:LocalizedString ToLocalizedString(this LocRef)——当 KeyId 不为 0 时按它指向,否则按键名指向,而一个空引用会转换成一个空的 LocalizedString。它只有在安装了 com.unity.localization 时才存在,位于版本 define SHEETFORGE_LOCALIZATION 之下——与 Addressables 那一层里 SHEETFORGE_ADDRESSABLES 所用的是同一套安排
SheetForgeRuntimeInfo(静态)const Version

生成类型(模式——按项目而定,非随包发布的 API)

对于每个标签页 Foo,代码生成会在你的 generatedNamespace 中生成:

public sealed partial class FooDefinition    // one strongly-typed field per column; @desc → doc/tooltip
public sealed partial class FooDatabase : DefinitionDatabase
{
    // TabName, Count, SchemaFingerprint, Records, RecordsUntyped override,
    // lazy _byId/_byIntId lookups, InvalidateIndex override
}

使用 SheetForgeDatabases.LoadAsync<FooDatabase>("Foo") 加载。

两个类都以 partial 形式生成,因此你可以在生成文件旁边的自己的文件中添加派生成员——一个计算属性、一个接口实现、一个运算符——重新导入不会覆盖它。有一条边界:不要在你自己的那部分中添加任何序列化字段。烘焙出的 ScriptableObject 每次导入都会从工作表重新构建,因此任何只存在于你那部分里的序列化内容,都会回到其默认值——如果一个取值属于数据,它就应该属于某一列。(partial 关键字不会影响 SchemaFingerprint,它只根据架构本身计算,因此把这些类改成 partial 不会使任何一次既有的烘焙失效。)


其他程序集

  • SheetForge.Setup——无依赖的 Addressables 缺失引导程序。没有公共 API(全部是 internal;它的存在只是为了显示一个引导窗口)。

  • SheetForge.PluginDemo(一个合并后的 asmdef + 一个 Demo.Editor asmdef;内容命名空间仍为 SheetForge.Skills)——参考示例包,不是产品 API 的一部分。它包含:

    • SkillsPlugin(七个插件接口——基础、验证器、边、模板、图形、代码注册表、主题);
    • Modifier + ModifierCellParser(自定义单元格类型)、ModifierStatEdgeContributor(边贡献者);
    • ExamplePipelineAugmenter / ExampleReactiveAugmenter(画布覆盖)、ExampleCodeAtoms_Refs 代码注册表);
    • ExampleStudioUi(声明式动作、面板、列徽标与单元格编辑器提示)、ExampleImportObserver(管线观察者);
    • ExampleStageStripWidget / ExampleInspectorAction / ExampleStudioPanel(Data Studio 的 Editor 扩展点,颜色取自公共调色板)、ExampleLocStrings(用两种语言注册那些标签——位于程序集中,因此浏览器也能看到它们);
    • 一条程序集级别的 SheetForgePluginCompat 声明;
    • SkillRunner(消费运行时数据)、位于默认命名空间 SheetForge.Generated 中生成的 Example* 类型(隔离依靠的是 Example* 前缀,而不是独立的命名空间)。

    不含插件的 SheetForge.CoreDemo 示例以 asmdef 随包发布(直接编译进 Assembly-CSharp)。

相关页面