TiXL 设置系统重构指南:三层设置模型与编辑器状态持久化架构解析
【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3
本篇技术指南以 TiXL(t3)项目中的设置重构规划文档(Plan_ProjectSettings.md)为骨架,系统讲解 TiXL 当前采用的三层设置模型——App 级(CoreSettings)、Composition 级(CompositionSettings)、Project-global 级(根 Operator 的.t3ui状态),以及渲染设置、输出窗口状态、时间线状态、窗口布局等编辑器状态的持久化机制。读完本文,你将掌握 TiXL 各类设置分别存于哪个文件、由哪个访问器读取、如何通过 breadcrumb 继承、如何进行旧格式迁移与克隆,并能据此理解或排查 TiXL 项目中"设置不生效 / 状态丢失 / 布局未恢复"一类问题。
一、背景与动机:一次"同名异义"驱动的设置重构
在 TiXL 早期代码中,ProjectSettings这个名字同时被用于全局设置与每个 Symbol 的播放设置,导致以下混乱:
- 全局的
ProjectSettings(保存于projectSettings.json)与 per-symbol 的PlaybackSettings命名歧义; - 部分本应属于单个 Composition 的字段(IO、性能开关)却散落在应用级配置里;
- 设置界面长期以弹窗(Popup)形式存在,缺乏格式版本管理与窗口化交互;
- 切换 Composition 时渲染设置存在"引用共享"的克隆缺陷。
重构的核心动作(对应规划文档 "Completed Work" 部分)可以归纳为三次重命名 + 一次分层:
| 重构前 | 重构后 | 序列化位置 | 用途 |
|---|---|---|---|
全局ProjectSettings | CoreSettings | projectSettings.json | 应用级设置(保持原文件名不变) |
per-symbolPlaybackSettings | CompositionSettings(历经ProjectSettings→SymbolSettings过渡) | .t3文件 | 每个 Composition 的播放 / 音频 / 导出配置 |
Symbol.ProjectSettings | Symbol.CompositionSettings | — | Symbol 数据模型上的挂载点 |
同时,设置类整体迁移到T3.Core.Settings命名空间(目录 Core/Settings),FileLocations与UserData也随之迁入;GlobalMute/GlobalPlaybackVolume更名为AppMute/AppVolume;IO 与性能配置被移回CoreSettings(它们是应用级而非 per-composition 的)。出于向后兼容,.t3文件中的 JSON key 仍保留"ProjectSettings"。
二、三层设置模型总览
重构后的架构将设置严格划分为三个层级,每一层都有独立的存储文件、生命周期与访问入口:
| 层级 | 存储位置 | 典型内容 | 访问方式 |
|---|---|---|---|
| Project-global(项目全局) | 根 Operator 的.t3ui | 输出窗口状态、窗口布局与可见性;IO(OSC 端口)与性能开关则在CoreSettings | OutputWindow.State(经_lastSyncedSymbolUi读取根 op 的 SymbolUi) |
| Per-symbol Composition(随 Composition 继承) | 所在 Symbol 的.t3(渲染设置与时间线状态在.t3ui) | 播放配置(BPM、soundtrack、音频源、同步方式)、音频混音、导出配置、渲染设置、时间线视图状态 | CompositionSettings.Current(breadcrumb 向上遍历继承) |
| App-level(应用级) | projectSettings.json | AppMute/AppVolume、MIDI 捕获限制、默认 OSC 端口、性能与日志开关 | CoreSettings.Config、UserSettings.Config |
三个层级中最关键的设计意图是:随项目走的数据进入.t3/.t3ui,随机器/用户走的数据进入projectSettings.json与用户配置文件。例如音频输入设备在CompositionSettings.Playback.AudioInputDeviceName中按项目记录,而机器特定的默认输入设备则通过CoreSettings.ConfigData.LocalAudioInputDeviceName保留在projectSettings.json中,从而保证共享项目在另一台机器上仍能解析到真实输入设备。
三、App 级设置:CoreSettings 与 projectSettings.json
CoreSettings(源码:Core/IO/CoreSettings.cs)是跨 Core、Editor、Player 三端共享的全局应用设置,序列化到设置目录下的projectSettings.json(文件名在重构中刻意保留,避免迁移成本)。其ConfigData内嵌类包含:
| 字段 | 默认值 | 说明 |
|---|---|---|
AppMute/AppVolume | false/1 | 全局静音与音量(由工具栏音频 toggle 直接切换,而非 per-project 的 SoundtrackMute) |
LimitMidiDeviceCapture | null | 限制 MIDI 设备捕获 |
DefaultOscPort | 8000 | 默认 OSC 端口 |
LocalAudioInputDeviceName | 空 | 机器特定的 WASAPI 输入设备,保持共享项目可移植 |
TimeClipSuspending | true | 时间剪辑挂起优化开关 |
SkipOptimization | false | 跳过优化 |
EnableDirectXDebug | false | DirectX 调试层开关 |
EnableBeatSyncProfiling | false | Beat 同步性能剖析 |
LogAssemblyVersionMismatches/LogCompilationDetails/LogAssemblyLoadingDetails/LogFileEvents | false | 各类日志开关 |
UseProcessScopedShadowCopies | false | 4.3 之前的进程级 shadow copy 逃生舱 |
底层机制:Settings<T> 基类
CoreSettings继承自通用的 Core/IO/Settings.cs 中的Settings<T>基类,该基类承担了所有 JSON 配置文件的读写职责:
- 通过
Settings<T>.Defaults提供默认值,Settings<T>.Config为当前生效实例; - 构造时从
FileLocations.SettingsDirectory下加载 JSON;文件缺失时以默认值新建; - 若文件存在但无法读取(被其他实例锁定或损坏),会保留磁盘文件不动(
_preserveFileOnDisk),避免退出时用默认值覆盖用户真实配置,并记录警告; - 注册
ProcessExit事件在退出时自动Save(); - 子类可重写
OnBeforeSave()在序列化前归一化数据。
四、Composition 级设置:CompositionSettings 与 breadcrumb 继承
CompositionSettings(源码:Core/Settings/CompositionSettings.cs)是本次重构的核心成果——每个 Symbol 可以拥有自己的项目设置,并且通过图层次(breadcrumb)向上遍历继承:当某 Composition 未定义设置时,会沿父级查找,直到找到最近的"定义设置"的 Symbol。CompositionSettings.Current静态访问器等价于Animation.Playback.Current?.Settings ?? Defaults,即 null 安全地回退到默认值。
4.1 顶层结构与主要枚举
public sealed class CompositionSettings { public static CompositionSettings Defaults { get; } = new(); public static CompositionSettings Current => Animation.Playback.Current?.Settings ?? Defaults; public bool Enabled { get; set; } public PlaybackConfig Playback { get; init; } = new(); public AudioMixConfig Audio { get; init; } = new(); public ExportConfig Export { get; init; } = new(); public ProxyConfig Proxy { get; init; } = new(); }Enabled是"本 Symbol 是否主动定义设置"的总开关——关闭时 UI 会提示当前继承自哪个父 Composition。其中几个关键枚举(在 CompositionSettings.cs 的#region Enums中定义):
AudioSources:ProjectSoundTrack(时间线驱动的项目配乐)/ExternalDevice(外部设备);SyncModes:Timeline/Tapping(手动打拍子驱动时钟);BeatLockSources:OnsetDetection(经典瞬态检测)/PhaseModel(DanceAi 神经网络的 bar-phase 模型)/PhaseModelRaw(网络原始输出,供对比);BpmChangeModes:StretchWithBeat(保持 bar 数值不变,内容随 BPM 变速)/KeepSeconds(保持秒数不变,仅移动网格)。
4.2 四个配置子类与默认值
PlaybackConfig(播放配置)
| 字段 | 默认值 | 说明 |
|---|---|---|
Bpm | 120 | 项目速度;TiXL 动画单位是 bar,BPM 直接控制动画速度 |
OnBpmChange | — | BPM 修改对 bar 定时内容(剪辑位置、关键帧、循环范围)的处理方式 |
AudioClips | 空列表 | 时间线音频剪辑(TimelineAudioClip列表) |
AudioSource | — | 音频源:项目配乐或外部设备 |
Syncing | — | 同步方式:Timeline 或 Tapping |
UsesBeatTapping | 计算属性 | AudioSource == ExternalDevice && Syncing == Tapping时成立 |
AudioInputDeviceName | 空 | 输入设备名,空 = 用默认输入 |
AudioGainFactor/AudioDecayFactor | 1/0.9 | 输入信号增益与 [AudioReaction] 的衰减因子 |
EnableAudioBeatLocking | true | 是否启用音频节拍锁定(编辑器会寻找低音瞬态、hi-hat、军鼓并锁定播放速度) |
BeatLockSource/BeatLockSmoothing | — /0.5 | 节拍锁定来源与平滑度(0 紧跟模型可能抖动,1 信任运行节奏缓慢修正) |
BeatLockAudioOffsetSec | 0 | 相位偏移(±1s 范围),用于补偿视频处理设备造成的输出延迟 |
AudioMixConfig(音频混音)
| 字段 | 默认值 | 说明 |
|---|---|---|
SoundtrackMute/OperatorMute | false/false | 配乐 / Operator 静音 |
SoundtrackVolume/OperatorVolume | 0.5/1 | 配乐 / Operator 音量 |
AudioResyncThreshold | 0.04 | 音频偏离动画超过该阈值(推荐 0.02s~0.05s)时触发重新同步 |
ExportConfig(导出可执行文件配置):Title/Author(空则分别回退到 Operator 名/包名)、DefaultWindowMode = Fullscreen、EnablePlaybackControlWithKeyboard = true(方向键跳转 + 空格暂停)、PreferredWidth/Height = 1920×1080、ShowLogs = false、SkipStartupDialog = false(仍可用--dialog强制呼出)、StripUnusedOperators = true(只打包与导出输出相连的 Operator 及自动播放的音频 Operator)。
ProxyConfig(预览代理配置):Format = ProRes(仅限 all-intra/LGPL 编码,绝不使用 H.264/HEVC 即 libx264/libx265,因其为 GPL)、Resolution = 0.5(源分辨率的比例)、UseForPreview = true(预览/拖动时播放代理,渲染始终用全分辨率源)。
4.3 序列化与旧格式迁移
CompositionSettings.WriteToJson仅在Enabled、存在音频剪辑或 Proxy 非默认时写出;写出的 JSON key 为"ProjectSettings"(顶层对象下),包含Playback、Audio、Export、Proxy四个子对象。读取端(ReadFromJson)则做了三重兼容:
- 新格式优先:优先取
"ProjectSettings"key,若其下存在"Playback"子对象则按新嵌套格式读取; - 旧 key 兼容:找不到
"ProjectSettings"时回退到旧的"PlaybackSettings"key; - 旧扁平格式迁移:若
"ProjectSettings"内没有"Playback"子对象,按旧版扁平格式读取——包括将音频剪辑从 Symbol 根或 settings 根读取,以及BPM 迁移:旧项目把 BPM 存在剪辑本身,新模型只保留在Playback.Bpm;加载时若发现Playback.Bpm == 0而主配乐剪辑带LegacyBpmForMigration,会一次性复制进Playback.Bpm并自动Enabled = true。
此外,加载音频剪辑时对"绝对路径已不存在的非主配乐剪辑"会直接丢弃并告警(IsUnmanageableMissingClip),因为这类死引用没有 UI 可以移除,避免每帧重复注册和报错;相对包路径的剪辑则保留,交由资源消费者延迟解析。
4.4 克隆:用序列化做深拷贝
规划文档特别提到"修复了切换 Composition 时克隆 bug(原来是共享引用,现在克隆)"。CompositionSettings.Clone()的实现策略是通过自己的序列化往返深拷贝:先把设置写入 JSON,再ReadFromJson读回,保证克隆结果与源设置的 save/load 往返完全一致——用于将 Symbol 复制为新类型时隔离设置实例。
五、Project-global 设置:根 Operator 的 .t3ui 状态
这一层存储于根 Operator 的.t3ui(整个项目只有一份),包含输出窗口状态与窗口布局。规划文档强调这些数据"属于项目"而非"属于某个 Composition",因此读取时使用ProjectView.Focused.RootInstance而非当前 Composition。
5.1 OutputWindowState(输出窗口状态)
源码 Editor/Gui/Windows/Output/OutputWindowState.cs 定义了每个输出窗口的持久化状态,以 JSON 数组形式存储在.t3ui的"OutputWindows"key 下,支持多个输出窗口:
- Gizmo:
ShowGizmos(默认On)、TransformGizmoMode(默认Move)——直接以State作为后备存储(无拷贝); - 背景与相机:
BackgroundColor、CameraControlMode(默认AutoUseFirstCam,另有SceneViewerFollowing/UseViewer/PickedACamera)、CameraPosition(默认[0,0,2.414],即默认相机距离)、CameraTarget、CameraRoll、CameraSpeed——基于拷贝,每帧通过SyncCopyFieldsToState()同步; - 分辨率:
ResolutionTitle、ResolutionWidth/Height、ResolutionUseAsAspectRatio; - Pinning(钉住):
IsPinned、PinnedInstancePath(Guid[])、PinnedOutputId。
5.2 TimelineState(时间线视图状态)
源码 Editor/Gui/Windows/TimeLine/TimelineState.cs 存储每个 Symbol 的.t3ui中("Timeline"key)。刻意只持久化视图状态:ScaleX、ScrollX、Mode(DopeView/CurveEditor),而 Loop 范围与播放位置保持为Playback对象上的运行时状态,不落盘。切换 Composition 时通过SyncStateWithComposition完成保存/加载。此外还包含TimelineHeight(项目级窗口布局,仅根 Symbol 的副本会被写入)、InlineDataClipEditEnabled、DetailsAreaHeight(内联编辑面板高度,默认 200px)、SourceExtent(Symbol 有意义内容的 bar 范围,供 TimeClip 使用)等字段。
5.3 窗口布局与可见性
SymbolUi.WindowLayout——ImGui 布局 INI 字符串(经SaveIniSettingsToMemory()获取);SymbolUi.WindowLayoutImGuiVersion——版本标记,仅当 ImGui 主版本号一致时才恢复布局,避免跨版本布局错乱;SymbolUi.WindowVisibility——Dictionary<string, bool>,窗口标题 → 可见性;- 保存时机为
ProjectView.Close(),恢复时机为ProjectView.SetAsFocused(); - 通过
UserSettings.Config.SaveWindowLayoutsWithProjects(默认true,见 Editor/Gui/UiHelpers/UserSettings.cs)选择是否随项目保存布局。
规划文档还记录了该层的三处修复:ProjectView.Close不再调用Unpin()而是保存状态;Pinning 在项目关闭/重开后仍能存活(陈旧的 ProjectView 会重新解析到当前实例);切换项目时状态保存到正确的项目(跟踪_lastSyncedSymbolUi)。
六、渲染设置:RenderSettings 直接读写 SymbolUi
规划文档提到"移除了ForNextExport中间层——RenderSettings.Current直接从SymbolUi.RenderSettings读写"。源码 Editor/Gui/Windows/RenderExport/RenderSettings.cs 印证了这一点:
RenderSettings.Current从聚焦的 Composition的 SymbolUi 读取;首次访问时以默认值 + 旧版路径迁移初始化(迁移UserSettings中遗留的RenderVideoFilePath、RenderSequenceFilePath、RenderSequenceFileName、RenderSequencePrefix),且不标记为已修改——因为该 getter 会被纯 UI 读取触发(如输出窗口的渲染提示),不应弄脏 Composition;- 字段集:
TimeReference(Bars/Seconds/Frames)、StartInBars、EndInBars(默认8)、FrameRate(默认60)、OverrideMotionBlurSamples(默认-1,即不覆盖)、RenderMode(Video/ImageSequence)、VideoCodec(默认 H264)、Bitrate(默认25_000_000)、AutoIncrementVersionNumber/CreateSubFolder/AutoIncrementSubFolder(默认true)、ExportAudio(默认true)、FileFormat、TimeRange(Custom/Loop/Soundtrack/Continuous)、连续捕获时钟(Realtime/Deterministic)与帧率模式(FixedFps/Variable,Variable 保留未实现)、ResolutionFactor(默认1)以及渲染路径VideoFilePath = "./Render/render-v01.mp4"、SequenceFilePath = "./ImageSequence/"、SequenceFileName = "v01"、SequencePrefix = "render"; - 序列化 key 为
.t3ui中的"RenderExport";Clone()/CopyFrom()用于复制与切换 Composition 时隔离实例。
渲染导出时,输出窗口使用的是RenderProcess.GetActiveOrRequestedSettings()而不是Current,确保"正在渲染中"与"UI 当前显示"互不干扰。另外修复了AddInt重置按钮因缺少isDefault检查而从不高亮的问题。
七、Composition Settings 窗口:从弹窗到可停靠窗口
规划文档记录设置界面已从 Popup 升级为可停靠的Window(类ProjectSettingsWindow)。源码 Editor/Gui/Windows/TimeLine/ProjectSettingsWindow.cs 显示其标题为 "Composition Settings",左侧导航分五个分类:
| 分类 | 面板标题 | 核心内容 |
|---|---|---|
Playback | Timing | 项目模式(Animation=ProjectSoundTrack / Live Interactive=ExternalDevice)、BPM、On BPM Change 行为、Sync Mode、Beat Lock 源与平滑度、Beat Sync Offset |
Audio | Project Audio | Resync Threshold、Main Volume、输入设备选择、Gain/Decay、电平表、Main Soundtrack 管理 |
Proxies | Video Proxies | Use proxies for preview、Proxy Format(ProRes/Hap/Hap Alpha/Hap Q)、Resolution 比例、最小可用磁盘、代理存储清理 |
Recording | Recording | Record 按钮捕获范围:Capture Audio、Capture IO(MIDI/OSC 子开关) |
Executable | Export | Title/Author、Window Mode、键盘播放控制、Preferred 分辨率、Skip Startup Dialog、Show Logs、Strip Unused Operators 与导出按钮 |
该窗口的交互细节与文档互相印证:
- 顶部主开关 "Specify settings for <SymbolName>" 控制
Enabled;未定义时提示 "Currently inheriting settings from …"; - Soundtrack 由 [AudioClip] Operator 拥有:窗口只负责"创建或聚焦"主配乐——"Create Soundtrack" 会以一条可撤销的宏命令添加
AudioClipop(AutoPlay=true、Display=BackgroundImage、Style=Waveform),剪辑的路径、偏移、修剪全部交给该 op;CompositionSettings.TryGetMainSoundtrack也同时支持设置列表与 op 提供的剪辑两种来源; - 窗口各面板的修改统一通过
SymbolUi.FlagAsModified()标记脏状态; - 切换项目模式时
UpdatePlaybackAndTimeline会同步切换Playback.Current(时间线或打拍时钟)、时间线可见性、归一化遗留的Syncing值。
在菜单与入口方面:App 菜单显示 "Composition Settings" 并在当前聚焦 op 定义设置时带勾选标记;时间线齿轮图标切换窗口可见性;工具栏音频 toggle 切换的是应用级AppMute(而非 per-project 的 SoundtrackMute)。
八、访问模式速查
规划文档归纳了五个访问入口,全部与源码一一对应:
| 访问器 | 语义 | 源码依据 |
|---|---|---|
CompositionSettings.Current | per-symbol Composition 配置,null 安全回退 Defaults,breadcrumb 继承 | Core/Settings/CompositionSettings.cs |
RenderSettings.Current | 直接读写聚焦 SymbolUi 的渲染设置,lazy-init + 旧版迁移 | Editor/Gui/Windows/RenderExport/RenderSettings.cs |
OutputWindow.State | 经_lastSyncedSymbolUi读写根 op SymbolUi 的输出窗口状态 | Editor/Gui/Windows/Output/OutputWindowState.cs |
CoreSettings.Config | App 级设置(projectSettings.json) | Core/IO/CoreSettings.cs |
UserSettings.Config | 用户偏好(含SaveWindowLayoutsWithProjects、ViewedCanvasAreaForSymbolChildId等) | Editor/Gui/UiHelpers/UserSettings.cs |
九、后续演进方向
规划文档的 "Future Work" 部分列出了尚未完成的演进计划(从源码结构看,其中部分已有雏形):
- Graph View State:每个 Symbol 的画布位置与缩放(
UserSettings.ViewedCanvasAreaForSymbolChildId已有部分实现)、选中 Operator 集合的持久化; - 默认 Composition 设置:计划在
UserSettings.ConfigData增加DefaultCompositionSettings字段,让新 Composition 从用户默认值初始化,并使 Composition Settings 窗口的"恢复默认"按钮使用该默认值; - 自动视觉测试:以编辑器状态持久化为前提——加载测试项目 → 恢复编辑器状态 → 截图 → 与参考图对比(见规划文档中提及的另一份文档 Plan_AutomaticTests.md),状态持久化是确定性测试截图的前置条件;
- 其他:每项目首选分辨率/宽高比列表、每项目 key-value 参数存储、已钉住的动画参数(
DopeSheetArea.PinnedParametersHashes)。
这些内容属于规划中的方向而非已落地功能,读者在阅读源码时应以当前实际实现为准。
总结
TiXL 的设置系统经过本轮重构后形成了清晰的分层边界:应用级数据进projectSettings.json,随 Composition 的数据进.t3(CompositionSettings,JSON key"ProjectSettings"兼容旧版),项目全局的窗口/布局/输出窗口状态进根 Operator 的.t3ui。CompositionSettings.Current的 breadcrumb 继承、RenderSettings.Current的直接读写、基于序列化的深拷贝克隆、以及Settings<T>基类的损坏保护与退出自动保存,共同构成了这套既可移植共享、又可逐机定制的持久化架构。对于想要为 TiXL 贡献新设置项或排查状态持久化问题的开发者而言,本文的层级对照表与访问模式速查是快速定位的入口。
【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考